深入了解 Laravel 的依赖注入容器
发布于 作者 Steve McDougall
Laravel 拥有一个很棒的依赖注入容器,但很多人却避而远之。在本教程中,我将介绍如何利用 Laravels 容器让我的代码为我工作。
使用容器的关键在于组织 - 在一个一致的地方保存容器绑定,并使用有意义的命名约定,以便你能够了解正在发生的事情。毕竟,容器的有效性取决于放入其中的内容。
假设我们希望将所有绑定都放在一个地方,例如在服务提供者中。这听起来很合理吧?但当我们的应用程序不断发展时会发生什么?我们从一个简单的应用程序开始,可能只有 5-6 个绑定,然后添加了一些新功能,需要向容器添加更多绑定。不知不觉中,我们使用的服务提供者变得非常庞大,想要找到任何东西都需要花费大量的认知精力。
我们该如何解决这个问题?如何确保我们不是仅仅将绑定塞进服务提供者中来掩盖问题?让我带你了解我的方法。
我的主要 AppServiceProvider
是应用程序的入口点,因此我的工作是注册其关键区域。我依赖于领域驱动设计,因此每个领域都有一个服务提供者。我允许每个领域干净地管理其绑定。
final class AppServiceProvider extends ServiceProvider{ public function register(): void { $this->app->register( provider: AuthDomainServiceProvider::class, ); $this->app->register( provider: CommunicationDomainServiceProvider::class, ); $this->app->register( provider: WorkDomainServiceProvider::class, ); }}
使用这种方法,我可以根据需要启用和禁用领域,快速添加新领域,并从一个文件中概述应用程序中的所有领域。
当然,我会保留 Laravel 应用程序中的其他默认服务提供者,因为它们有自己的用途。现在让我们深入了解其中一个领域服务提供者,以理解它的用途。
final class AuthDomainServiceProvider extends ServiceProvider{ public function register(): void { $this->app->register( provider: QueryServiceProvider::class, ); $this->app->register( provider: CommandServiceProvider::class, ); $this->app->register( provider: FactoryServiceProvider::class, ); }}
因此,我的身份验证服务提供者纯粹用于注册需要写入绑定的领域方面。
查询 - 这些是应用程序中的读取操作,是我需要执行的常见查询,或者是我需要执行的查询的一部分。我写了一篇关于我的方法的教程 这里.
命令 - 这些是应用程序中的写入操作。通常情况下,这些操作会被拉入后台作业,以便应用程序能够快速响应。
工厂 - 这些是数据对象工厂。我发现数据对象会变得很大很混乱,占用大量空间。我的解决方案是将它们移到专门的工厂中,以便我可以使用它们在应用程序中创建数据对象。
让我们看一下 CommandServiceProvider
,以及我们如何在应用程序中有效地注册命令。
final class CommandServiceProvider extends ServiceProvider{ public array $bindings = [ FindOrCreateUserContract::class => FindOrCreateUser::class, GenerateApiTokenContract::class => GenerateApiToken::class, SendPasswordResetContract::class => SendPasswordReset::class, ];}
Laravel 允许你使用服务提供者上的 bindings
属性注册不需要传递参数的任何绑定。这可以使我们的服务提供者保持整洁。
让我们看一下这些绑定之一,以理解它们的外观和用途。
interface GenerateApiTokenContract{ public function handle(Authenticatable $user, DataObjectContract $payload): Model|NewAccessToken;}
然后我们继续进行实现。
final class GenerateApiToken implements GenerateApiTokenContract{ public function handle(Authenticatable $user, DataObjectContract $payload): Model|NewAccessToken { return DB::transaction( fn (): Model|NewAccessToken => $user->createToken( name: $payload->name, ), ); }}
我们将写入操作包装在一个数据库事务中,然后使用注入的用户模型,并调用其上的 create token 方法,将 payload 中的 name 属性传递进去。这样可以保持代码整洁 - 因为你也可以使用它为应用程序中的任何用户生成 API 令牌,而不仅仅是当前登录的用户。
以这种方式使用容器意味着我的控制器始终保持干净和简洁。让我们看一下登录用户的 API 控制器示例。
final readonly class LoginController{ public function __construct( private GenerateApiTokenContract $command, private TokenNameGenerator $generator, ) {} public function __invoke(LoginRequest $request): Responsable { $request->authenticate(); return new TokenResponse( data: TokenFactory::make( data: $this->command->handle( user: auth()->user(), payload: new TokenRequest( name: $generator->generate(), ), ), ), ); }}
当然,你可以随意拆分这段代码,例如为它提供更多空间。但对我来说,这就是我的目标。我依赖于容器,并利用 Laravel 的优势,通过将小的移动部件组合起来,实现最终目标。
技术作家,就职于 Laravel 新闻,开发者倡导者,就职于 Treblle。API 专家,经验丰富的 PHP/Laravel 工程师。 YouTube 直播主.