Laravel 13 门面提供了优雅的静态接口,本文深入探讨门面的高级用法。

门面基础

自定义门面

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
37
38
39
40
41
42
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

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

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

public static function getFacadeRoot()
{
return parent::getFacadeRoot();
}
}

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

public static function fake($jobsToFake = [])
{
static::swap(new QueueFake(
static::getFacadeRoot(),
$jobsToFake
));
}
}

实时门面

实时门面使用

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
<?php

namespace App\Services;

use Facades\App\Services\PaymentService;
use Facades\App\Services\NotificationService;
use Facades\App\Services\CacheService;

class OrderService
{
public function process(int $orderId): array
{
$order = $this->getOrder($orderId);

PaymentService::charge($order);

NotificationService::send($order->user, 'order.processed');

CacheService::forget("order:{$orderId}");

return $order;
}

protected function getOrder(int $orderId): Order
{
return Order::findOrFail($orderId);
}
}

门面代理

门面代理模式

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
37
38
39
40
41
42
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

abstract class BaseFacade extends Facade
{
protected static array $proxies = [];

public static function proxy(string $method, callable $callback): void
{
static::$proxies[$method] = $callback;
}

public static function __callStatic($method, $args)
{
if (isset(static::$proxies[$method])) {
return call_user_func_array(static::$proxies[$method], $args);
}

return parent::__callStatic($method, $args);
}
}

class StorageFacade extends BaseFacade
{
protected static function getFacadeAccessor(): string
{
return 'filesystem';
}

public static function url(string $path): string
{
return static::getFacadeRoot()->url($path);
}

public static function temporaryUrl(string $path, \DateTimeInterface $expiration): string
{
return static::getFacadeRoot()->temporaryUrl($path, $expiration);
}
}

门面测试

门面伪造

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
37
38
39
40
41
42
43
44
<?php

namespace Tests\Unit;

use App\Facades\PaymentFacade;
use App\Facades\NotificationFacade;
use App\Services\OrderService;
use Mockery;
use Tests\TestCase;

class OrderServiceTest extends TestCase
{
public function test_order_processing()
{
PaymentFacade::shouldReceive('charge')
->once()
->withArgs(function ($order) {
return $order->id === 1;
});

NotificationFacade::shouldReceive('send')
->once()
->withArgs(function ($user, $type) {
return $type === 'order.processed';
});

$service = app(OrderService::class);
$result = $service->process(1);

$this->assertNotNull($result);
}

public function test_payment_failure()
{
PaymentFacade::shouldReceive('charge')
->once()
->andThrow(new \Exception('Payment failed'));

$this->expectException(\Exception::class);

$service = app(OrderService::class);
$service->process(1);
}
}

门面服务

门面服务绑定

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
<?php

namespace App\Providers;

use App\Facades\PaymentFacade;
use App\Services\PaymentService;
use App\Services\PaymentInterface;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('payment', function ($app) {
return new PaymentService(
config('services.payment.key'),
config('services.payment.secret')
);
});

$this->app->alias('payment', PaymentInterface::class);
$this->app->alias('payment', PaymentService::class);
}

public function boot(): void
{
PaymentFacade::proxy('quickCharge', function ($amount, $currency = 'USD') {
return PaymentFacade::charge([
'amount' => $amount,
'currency' => $currency,
]);
});
}
}

门面宏

门面宏扩展

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;

class MacroServiceProvider extends ServiceProvider
{
public function boot(): void
{
Cache::macro('rememberForever', function ($key, $callback) {
return $this->remember($key, null, $callback);
});

Cache::macro('getOrSet', function ($key, $callback, $ttl = 3600) {
if ($this->has($key)) {
return $this->get($key);
}

$value = $callback();
$this->put($key, $value, $ttl);

return $value;
});

Response::macro('success', function ($data = null, $message = 'Success') {
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
]);
});

Response::macro('error', function ($message, $code = 400, $errors = []) {
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors,
], $code);
});

Response::macro('paginated', function ($paginator, $transformer = null) {
$data = $transformer
? $paginator->getCollection()->map($transformer)
: $paginator->items();

return response()->json([
'success' => true,
'data' => $data,
'meta' => [
'current_page' => $paginator->currentPage(),
'last_page' => $paginator->lastPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
],
]);
});
}
}

门面装饰器

门面装饰器模式

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
37
38
39
40
41
42
43
44
45
46
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class LoggingFacade extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'logging.decorator';
}

public static function decorate(string $facade, array $methods = []): void
{
$root = app($facade::getFacadeAccessor());

app()->singleton('logging.decorator', function () use ($root, $methods) {
return new class($root, $methods) {
protected $wrapped;
protected array $methods;

public function __construct($wrapped, array $methods)
{
$this->wrapped = $wrapped;
$this->methods = $methods;
}

public function __call($method, $arguments)
{
if (in_array($method, $this->methods)) {
\Log::info("Calling {$method}", $arguments);
}

$result = $this->wrapped->{$method}(...$arguments);

if (in_array($method, $this->methods)) {
\Log::info("Result of {$method}", ['result' => $result]);
}

return $result;
}
};
});
}
}

门面缓存

门面结果缓存

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
37
38
39
40
41
42
43
44
45
46
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Facade;

class CachedFacade extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'cached.service';
}

protected static array $cacheConfig = [];

public static function cacheMethod(string $method, int $ttl = 3600): void
{
static::$cacheConfig[$method] = $ttl;
}

public static function __callStatic($method, $args)
{
if (!isset(static::$cacheConfig[$method])) {
return parent::__callStatic($method, $args);
}

$key = static::getCacheKey($method, $args);
$ttl = static::$cacheConfig[$method];

return Cache::remember($key, $ttl, function () use ($method, $args) {
return parent::__callStatic($method, $args);
});
}

protected static function getCacheKey(string $method, array $args): string
{
return static::getFacadeAccessor() . ':' . $method . ':' . md5(serialize($args));
}

public static function forgetMethodCache(string $method, array $args = []): bool
{
$key = static::getCacheKey($method, $args);
return Cache::forget($key);
}
}

门面链式调用

链式门面

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
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class QueryFacade extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'query.builder';
}

public static function table(string $table): static
{
$instance = app(static::getFacadeAccessor());
$instance->from($table);

return new static;
}

public static function select(...$columns): static
{
$instance = app(static::getFacadeAccessor());
$instance->select(...$columns);

return new static;
}

public static function where(string $column, $operator, $value = null): static
{
$instance = app(static::getFacadeAccessor());
$instance->where($column, $operator, $value);

return new static;
}

public static function get(): array
{
$instance = app(static::getFacadeAccessor());
return $instance->get();
}

public static function first(): ?object
{
$instance = app(static::getFacadeAccessor());
return $instance->first();
}
}

门面集合

门面集合操作

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
37
38
39
40
41
42
43
44
<?php

namespace App\Facades;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Facade;

class CollectionFacade extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'collection.factory';
}

public static function make($items = []): Collection
{
return collect($items);
}

public static function range($from, $to): Collection
{
return collect(range($from, $to));
}

public static function times($count, callable $callback = null): Collection
{
return collect()->times($count, $callback);
}

public static function wrap($value): Collection
{
return collect()->wrap($value);
}

public static function unwrap($value): mixed
{
return $value instanceof Collection ? $value->all() : $value;
}

public static function empty(): Collection
{
return collect();
}
}

门面注册

门面别名注册

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
<?php

namespace App\Providers;

use App\Facades\PaymentFacade;
use App\Facades\StorageFacade;
use Illuminate\Support\ServiceProvider;

class FacadeServiceProvider extends ServiceProvider
{
protected array $facades = [
'Payment' => PaymentFacade::class,
'Storage' => StorageFacade::class,
];

public function register(): void
{
foreach ($this->facades as $alias => $facade) {
class_alias($facade, $alias);
}
}

public function boot(): void
{
$loader = \Illuminate\Foundation\AliasLoader::getInstance();

foreach ($this->facades as $alias => $facade) {
$loader->alias($alias, $facade);
}
}
}

总结

Laravel 13 门面提供了优雅的静态接口访问底层服务。通过自定义门面、实时门面、门面宏和门面测试等技术,可以构建简洁、可测试的代码。