什么是契约(Contracts)?
基本概念
契约是 Laravel 框架的接口,定义了核心服务应该提供的方法。
|
|
// 契约就是接口 namespace Illuminate\Contracts\Mail; interface Mailer { // 定义必须实现的方法 public function send($view, array $data = [], $callback = null); public function raw($text, $callback); public function to($users); public function cc($users); } |
契约的实际位置
|
|
// Illuminate\Contracts 命名空间下的接口 Illuminate\Contracts\Cache\Repository // 缓存契约 Illuminate\Contracts\Mail\Mailer // 邮件契约 Illuminate\Contracts\Auth\Authenticatable // 认证契约 Illuminate\Contracts\Queue\Queue // 队列契约 Illuminate\Contracts\Routing\ResponseFactory // 响应契约 |
什么是门面(Facades)?
基本概念
门面是服务的静态代理,为服务容器中的绑定提供了简洁的静态接口。
|
|
// 门面提供静态调用 Cache::get('key'); Mail::send(...); Auth::user(); Route::get(...); |
门面的工作原理
|
|
// Cache 门面的简化实现 class Cache extends Facade { protected static function getFacadeAccessor() { return 'cache'; // 返回服务容器中的绑定名称 } } // 实际调用过程 Cache::get('key'); // 等价于 app('cache')->get('key'); |
契约 vs 门面:实际代码对比
使用门面的方式
|
|
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Mail; class UserService { public function sendWelcomeEmail($userId) { // 使用门面(静态调用) $user = Cache::get("user:{$userId}"); Mail::send('emails.welcome', ['user' => $user], function ($message) use ($user) { $message->to($user->email)->subject('Welcome!'); }); } } |
使用契约的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Mail\Mailer as Mail; class UserService { public function __construct( private Cache $cache, private Mail $mail ) {} public function sendWelcomeEmail($userId) { // 使用契约(依赖注入) $user = $this->cache->get("user:{$userId}"); $this->mail->send('emails.welcome', ['user' => $user], function ($message) use ($user) { $message->to($user->email)->subject('Welcome!'); }); } } |
契约的深度解析
契约的优势
1. 明确的依赖关系
|
|
// 清楚地知道这个类需要什么 class OrderProcessor { public function __construct( private PaymentGateway $payment, // 明确需要支付功能 private Notification $notifier, // 明确需要通知功能 private Logger $logger // 明确需要日志功能 ) {} } |
2. 更好的测试能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class OrderProcessorTest extends TestCase { public function test_order_processing() { // 创建模拟对象 $paymentMock = $this->createMock(PaymentGateway::class); $notificationMock = $this->createMock(Notification::class); // 设置期望 $paymentMock->expects($this->once()) ->method('charge') ->willReturn(true); $processor = new OrderProcessor($paymentMock, $notificationMock); $result = $processor->process(new Order()); $this->assertTrue($result); } } |
3. 松耦合设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
// 业务代码只依赖接口 class ReportService { public function __construct(private FileStorage $storage) {} public function generateReport() { return $this->storage->save($report); } } // 可以轻松更换实现而不影响业务代码 $this->app->bind(FileStorage::class, LocalStorage::class); // 或者 $this->app->bind(FileStorage::class, S3Storage::class); // 或者 $this->app->bind(FileStorage::class, AzureStorage::class); |
自定义契约示例
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
|
// 定义自己的契约 namespace App\Contracts; interface PaymentGateway { public function charge($amount, $token); public function refund($chargeId); public function createCustomer($email); } // 实现契约 class StripeGateway implements PaymentGateway { public function charge($amount, $token) { // Stripe 具体实现 } public function refund($chargeId) { // Stripe 退款实现 } public function createCustomer($email) { // Stripe 客户创建 } } // 在服务提供者中绑定 $this->app->singleton(PaymentGateway::class, StripeGateway::class); // 使用 class OrderService { public function __construct(private PaymentGateway $payment) {} } |
门面的深度解析
门面的工作原理细节
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
|
// 1. 门面基类魔法方法 abstract class Facade { public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); // 从容器获取实例 return $instance->$method(...$args); // 调用实例方法 } protected static function getFacadeRoot() { return app(static::getFacadeAccessor()); } } // 2. 具体门面类 class Cache extends Facade { protected static function getFacadeAccessor() { return 'cache'; // 服务容器中的绑定名称 } } // 3. 调用过程分解 Cache::get('key'); // 转换为 app('cache')->get('key'); |
门面的优势
1. 代码简洁性
|
|
// 门面方式:非常简洁 $user = Auth::user(); $value = Cache::remember('key', 3600, function() { return heavyOperation(); }); Mail::to($user->email)->send(new WelcomeMail($user)); // 对比依赖注入方式 public function __construct( AuthManager $auth, CacheRepository $cache, Mailer $mailer ) { $user = $auth->user(); $value = $cache->remember(...); $mailer->send(...); } |
2. 快速开发和原型设计
|
|
// 快速测试一个想法 Route::get('/test', function() { Cache::put('test', 'value', 10); Mail::raw('Test email', function($message) { $message->to('test@example.com'); }); return 'Done'; }); |
3. 全局可访问性
|
|
// 在任何地方都可以使用 class HelperClass { public static function doSomething() { $user = Auth::user(); // 即使在静态方法中也能使用 Cache::put('key', 'value'); } } function global_helper() { Route::current(); // 在全局函数中也能使用 } |
自定义门面示例
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
|
// 1. 创建自定义服务 class AnalyticsService { public function track($event, $data = []) { // 追踪逻辑 } public function pageView($page) { // 页面浏览统计 } } // 2. 注册到服务容器 $this->app->singleton('analytics', function() { return new AnalyticsService(); }); // 3. 创建门面 class Analytics extends Facade { protected static function getFacadeAccessor() { return 'analytics'; } } // 4. 使用 Analytics::track('user_registration', ['user_id' => 1]); Analytics::pageView('/home'); |
契约 vs 门面:如何选择?
适合使用契约的场景
1. 重要的业务逻辑
|
|
// 订单处理:需要明确依赖和良好测试 class OrderProcessor { public function __construct( private PaymentGateway $payment, private InventoryManager $inventory, private ShippingService $shipping ) {} public function process(Order $order) { // 重要业务逻辑,需要清晰依赖关系 } } |
2. 可能更换实现的组件
|
|
// 文件存储:可能需要在不同环境使用不同驱动 class DocumentService { public function __construct(private FileStorage $storage) {} public function saveDocument($file) { // 开发环境用本地存储,生产环境用云存储 return $this->storage->store($file); } } |
3. 需要单元测试的类
|
|
class PaymentServiceTest extends TestCase { public function test_payment_failure_handling() { $gatewayMock = $this->createMock(PaymentGateway::class); $gatewayMock->method('charge')->willThrowException(new PaymentFailed); $service = new PaymentService($gatewayMock); $this->expectException(PaymentFailed::class); $service->processPayment(...); } } |
适合使用门面的场景
1. 简单的辅助功能
|
|
// 路由定义、配置获取等 Route::get('/api/users', [UserController::class, 'index']); Config::get('app.timezone'); Log::info('User logged in', ['user_id' => Auth::id()]); |
2. 快速原型开发
|
|
// 快速测试功能 public function quickTest() { $users = DB::table('users')->where('active', 1)->get(); Cache::put('active_users', $users, 60); Mail::to('admin@example.com')->send(new UserReport($users)); } |
3. 全局工具函数
|
|
// 在视图、辅助函数等地方 @if(Auth::check()) <span>Welcome, {{ Auth::user()->name }}</span> @endif function format_date($date) { return Carbon::parse($date)->format('Y-m-d'); } |
实际项目中的最佳实践
混合使用策略
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
|
// 控制器:可以使用门面保持简洁 class UserController extends Controller { public function index() { // 简单的数据获取使用门面 $users = DB::table('users')->paginate(15); return view('users.index', compact('users')); } public function store(CreateUserRequest $request, UserService $service) { // 重要业务逻辑使用契约注入 $user = $service->createUser($request->validated()); // 简单的通知使用门面 Mail::to($user->email)->send(new WelcomeEmail($user)); return redirect()->route('users.index'); } } // 服务类:使用契约保证可测试性 class UserService { public function __construct( private UserRepository $users, private Mailer $mailer, private Logger $logger ) {} public function createUser(array $data): User { // 重要业务逻辑,需要明确依赖 } } |
测试策略
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
|
class UserServiceTest extends TestCase { public function test_user_creation() { // 模拟契约依赖 $repoMock = $this->createMock(UserRepository::class); $mailerMock = $this->createMock(Mailer::class); $service = new UserService($repoMock, $mailerMock); // 测试业务逻辑 $user = $service->createUser([...]); $this->assertInstanceOf(User::class, $user); } public function test_controller_integration() { // 控制器测试可以使用门面模拟 Cache::shouldReceive('get')->once()->andReturn(null); $response = $this->get('/users'); $response->assertStatus(200); } } |
总结
契约(Contracts)的特点:
-
✅ 接口约束:明确的服务约定
-
✅ 依赖注入:清晰的依赖关系
-
✅ 易于测试:轻松创建模拟对象
-
✅ 松耦合:易于替换实现
-
❌ 代码稍多:需要注入多个依赖
门面(Facades)的特点:
-
✅ 简洁易用:静态调用,代码简洁
-
✅ 快速开发:适合原型和简单功能
-
✅ 全局访问:在任何地方都能使用
-
❌ 隐藏依赖:依赖关系不明确
-
❌ 测试稍复杂:需要门面模拟
选择建议:
-
业务核心逻辑 → 使用契约
-
辅助工具功能 → 使用门面
-
需要良好测试 → 使用契约
-
快速开发 → 使用门面
-
大型项目 → 倾向契约
-
小型项目/原型 → 倾向门面
理解两者的区别和适用场景,可以帮助你在合适的场合选择合适的技术,写出更优雅、可维护的 Laravel 代码。
总结:
1,门面是依赖注入的“快捷方式”,可以理解成是依赖注入的语法糖。
2,契约可以理解为laravel定义的一些接口。所有契约在GitHub有独立库,可作为解耦的包被第三方开发者直接使用。
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - laravel 契约和门面