Laravel 13 门面模式详解

门面(Facades)是 Laravel 提供的一种优雅的静态代理机制,让开发者可以使用简洁的静态语法访问服务容器中的服务。

门面基础

什么是门面

门面为服务容器中的类提供了一个静态接口,让代码更加简洁易读。

1
2
3
4
5
6
7
use Illuminate\Support\Facades\Cache;

// 使用门面
Cache::get('key');

// 等价于
app('cache')->get('key');

常用门面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;

Cache::put('key', 'value', 3600);
DB::table('users')->get();
Log::info('Message');
Mail::to($user)->send(new WelcomeEmail());

内置门面

Cache 门面

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Support\Facades\Cache;

Cache::put('key', 'value', 3600);
Cache::get('key', 'default');
Cache::has('key');
Cache::forget('key');
Cache::flush();

Cache::remember('users', 3600, function () {
return User::all();
});

Cache::tags(['users'])->put('active', $users, 3600);

DB 门面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Illuminate\Support\Facades\DB;

DB::table('users')->get();
DB::table('users')->where('active', true)->first();
DB::insert('insert into users (name, email) values (?, ?)', ['John', 'john@example.com']);

DB::transaction(function () {
DB::table('orders')->insert(['user_id' => 1]);
DB::table('products')->decrement('stock');
});

DB::listen(function ($query) {
Log::info($query->sql);
});

Log 门面

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Support\Facades\Log;

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

Log::channel('daily')->info('Message');
Log::stack(['single', 'slack'])->info('Message');

Route 门面

1
2
3
4
5
6
7
8
9
use Illuminate\Support\Facades\Route;

Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
Route::resource('posts', PostController::class);

Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});

Storage 门面

1
2
3
4
5
6
7
8
use Illuminate\Support\Facades\Storage;

Storage::put('file.txt', 'Contents');
Storage::get('file.txt');
Storage::exists('file.txt');
Storage::delete('file.txt');
Storage::url('file.txt');
Storage::disk('s3')->put('file.txt', 'Contents');

创建自定义门面

创建服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace App\Services;

class PaymentService
{
public function process(float $amount): array
{
return [
'status' => 'success',
'amount' => $amount,
'transaction_id' => uniqid(),
];
}

public function refund(string $transactionId): bool
{
return true;
}
}

创建门面类

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Payment extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'payment';
}
}

注册服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PaymentService;

class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind('payment', function ($app) {
return new PaymentService();
});
}
}

使用门面

1
2
3
4
use App\Facades\Payment;

$result = Payment::process(100.00);
$refunded = Payment::refund('txn_123');

实时门面

使用实时门面

1
2
3
use Facades\App\Services\PaymentService;

$result = PaymentService::process(100.00);

实时门面原理

Laravel 会自动将 Facades\ 命名空间下的类转换为对应的服务实例。

1
2
// Facades\App\Services\PaymentService
// 自动解析为 app(App\Services\PaymentService::class)

门面类参考

Auth 门面

1
2
3
4
5
6
7
8
use Illuminate\Support\Facades\Auth;

Auth::login($user);
Auth::logout();
Auth::user();
Auth::id();
Auth::check();
Auth::attempt(['email' => $email, 'password' => $password]);

Config 门面

1
2
3
4
5
use Illuminate\Support\Facades\Config;

Config::get('app.name');
Config::set('app.locale', 'zh');
Config::has('app.timezone');

Event 门面

1
2
3
4
5
use Illuminate\Support\Facades\Event;

Event::dispatch(new OrderShipped($order));
Event::listen(OrderShipped::class, SendNotification::class);
Event::subscribe(UserEventSubscriber::class);

Queue 门面

1
2
3
4
5
6
7
8
use Illuminate\Support\Facades\Queue;

Queue::push(new ProcessPodcast($podcast));
Queue::later(60, new SendEmail($user));
Queue::bulk([
new ProcessFile(1),
new ProcessFile(2),
]);

Validator 门面

1
2
3
4
5
6
7
8
9
10
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
]);

if ($validator->fails()) {
return $validator->errors();
}

View 门面

1
2
3
4
5
6
use Illuminate\Support\Facades\View;

View::make('welcome', ['name' => 'John']);
View::exists('emails.welcome');
View::share('key', 'value');
View::composer('profile', ProfileComposer::class);

门面 vs 依赖注入

使用门面

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
public function index()
{
$users = Cache::remember('users', 3600, function () {
return User::all();
});

return response()->json($users);
}
}

使用依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Illuminate\Contracts\Cache\Repository as Cache;

class UserController extends Controller
{
public function __construct(
protected Cache $cache
) {}

public function index()
{
$users = $this->cache->remember('users', 3600, function () {
return User::all();
});

return response()->json($users);
}
}

测试门面

模拟门面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Mail;

public function test_cache_facade(): void
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');

$result = Cache::get('key');
$this->assertEquals('value', $result);
}

public function test_queue_facade(): void
{
Queue::fake();

ProcessPodcast::dispatch($podcast);

Queue::assertPushed(ProcessPodcast::class);
Queue::assertPushedOn('podcasts', ProcessPodcast::class);
}

public function test_mail_facade(): void
{
Mail::fake();

Mail::to($user)->send(new WelcomeEmail());

Mail::assertSent(WelcomeEmail::class);
Mail::assertSent(WelcomeEmail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email);
});
}

门面间谍

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Support\Facades\Event;

public function test_event_spy(): void
{
Event::spy();

event(new OrderShipped($order));

Event::shouldHaveDispatched(OrderShipped::class);
Event::shouldNotHaveDispatched(OrderFailed::class);
}

门面辅助函数

全局辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// cache() 等价于 Cache::getFacadeRoot()
cache('key');
cache(['key' => 'value'], 3600);

// event() 等价于 Event::dispatch()
event(new OrderShipped($order));

// info() 等价于 Log::info()
info('Message', ['context' => 'data']);

// response() 创建响应
response()->json(['message' => 'Success']);

// redirect() 重定向
redirect()->route('home');

// view() 渲染视图
view('welcome', ['name' => 'John']);

门面最佳实践

1. 选择合适的场景

1
2
3
4
5
6
7
8
9
10
11
// 好的做法:简单操作使用门面
Cache::get('key');
Log::info('Message');

// 好的做法:复杂逻辑使用依赖注入
class PaymentController
{
public function __construct(
protected PaymentService $payment
) {}
}

2. 测试友好

1
2
3
4
5
// 好的做法:可以轻松模拟
Cache::shouldReceive('get')->andReturn('value');

// 不好的做法:直接实例化,难以测试
$cache = new CacheManager();

3. 保持一致性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 好的做法:项目中统一风格
class OrderController
{
public function index()
{
return Cache::remember('orders', 3600, fn () => Order::all());
}
}

// 不好的做法:混用风格
class OrderController
{
public function __construct(protected Cache $cache) {}

public function index()
{
return $this->cache->remember('orders', 3600, fn () => Order::all());
}
}

门面列表

门面服务容器绑定
Appapp
Authauth
Cachecache
Configconfig
DBdb
Eventevents
Filefiles
GateGate
Loglog
Mailmail
Queuequeue
Routerouter
Storagefilesystem
Viewview

总结

Laravel 门面提供了一种优雅的方式来访问服务容器中的服务。通过门面,可以使用简洁的静态语法调用方法,同时保持代码的可测试性。理解门面的工作原理和最佳实践,可以帮助开发者编写更优雅、更易维护的 Laravel 代码。记住在简单场景使用门面,在需要依赖注入的场景使用构造函数注入,并充分利用门面的模拟功能进行测试。