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