swiftly / dependency
简洁的依赖注入。
Requires
- php: ^7.4 || ^8
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3
- phpunit/phpunit: ^9.6
- vimeo/psalm: ^5.15
Suggests
- ext-json: To allow JSON configuration files
This package is auto-updated.
Last update: 2024-09-07 22:38:59 UTC
README
轻松构建对象层次结构。
主要设计用于SwiftlyPHP项目,此组件提供依赖容器和注入器的轻量级实现,允许您轻松配置和构建复杂对象树,而无需太多麻烦。
虽然不如Symfony或Laravel等容器的功能全面,但此组件提供了一种最小化、易于使用的界面来管理依赖。
安装
要安装库,请使用Composer
composer require swiftly/dependency
用法
基础
如果您曾经参与过一个大型项目,您将知道将代码拆分为明确定义的类的益处。虽然这在使文件系统(和项目结构)更干净方面具有实际好处,但它还允许我们分离出相关的关注点,以易于推理和组合的方式将相关的功能组合在一起。
然而,随着时间的推移,您可能会拥有大量的类。对象将开始依赖其他对象。您会得到需要两个或三个对象构造函数的对象,而这些对象的构造函数又需要相同的对象,很快就会形成一个难以管理的复杂层次结构。
进入服务容器
<?php use Swiftly\Dependency\Container; $container = new Container();
服务容器是一个注册表,您可以将其中的应用程序中的类详细信息输入其中。
例如,假设我们有一个如下所示的类
<?php // MyClass.php class MyClass { public function __construct( private string $name, private int $age ) {} public function speak() { return "Hi, my name is " . $this->name . " and I am " . $this->age; } }
我们可以这样将其注册到容器中
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(MyClass::class) ->setArguments([ 'name' => 'John', 'age' => 42 ]);
在这里,我们已经让容器知道我们的自定义类,并提供了构建它所需的参数。现在,当我们需要MyClass
的实例时,我们可以调用容器的get()
方法,它会为我们创建一个副本。
<?php // ... continued from above ... $myclass = $container->get(MyClass::class); // "Hi, my name is John and I am 42" echo $myclass->speak();
这种方法的优点是,我们的对象仅在显式请求时才构造。如果我们从未从容器中请求它,它就永远不会被创建,从而节省了可能昂贵的实例化。
对象层次结构
现在我们已经解决了基础知识,让我们看看如何使用容器使创建对象层次结构更容易。
想象以下类
<?php // classes.php class Person { public function __construct( public string $name ) {} } class Salary { public function __construct( public int $yearly ) {} } class Job { public function __construct( public string $title, protected Salary $salary ) {} } class Employee { public function __construct( Person $person, Job $role ) {} public function speak() { return "I'm " . $this->person->name ", I am a " . $this->job->title; } }
这里有4个类,其中Employee
的构建需要对Person
和Job
的引用,而Job
有自己的依赖,形式为Salary
对象。
传统上构建一个Employee
可能如下所示
<?php $person = new Person("Jim"); $salary = new Salary(42_000); $job = new Job("Developer", $salary); $employee = new Employee($person, $job); // "I'm Jim, I am a Developer" echo $employee->speak();
而使用我们的容器,它看起来像这样
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(Person::class)->setArguments(['name' => 'Jim']); $container->register(Salary::class)->setArguments(['yearly' => 42_000]); $container->register(Job::class)->setArguments(['title' => 'Developer']); $container->register(Employee:class); $employee = $container->get(Employee:class); // "I'm Jim, I am a Developer" echo $employee->speak();
我们不需要每次需要Employee
的实例时都构建一个新层次结构,只需注册我们的类一次,然后在需要时调用get()
。容器检查每个类的构造函数,沿着层次结构向下,根据需要解决每个类的要求。(如果不能解决,则抛出一个有用的异常)。
工厂
在创建对象时,您可能需要执行一些额外的设置,例如打开数据库连接或读取配置文件。这就是工厂发挥作用的地方。
在本质上,工厂只是创建和返回对象的函数。
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(Person::class, function () { return new Person("Jill"); }); $person = $container->get(Person::class); // "Jill" echo $person->name;
此代码的功能与上面的Person
示例相同,但与使用setArguments()
实用工具不同,值已在工厂中硬编码。
然而,一个更实际的例子可能如下所示
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(Database::class, function () { $database = new Database(...); $database->open(); return $database; });
现在每次我们get()
一个Database
的副本时,我们可以确信它已经打开并准备好使用。
太好了!但如果您的工厂也需要传入值呢?
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(HttpTransport::class, function (string $user_agent) { new CurlTransport($user_agent); })->setArguments([ 'user_agent' => 'PHP/Curl' ]); $container->register( HttpClient::class, function (HttpTransport $transport) { $client = new HttpClient($transport); $client->setTimeout(2000); $client->setBlocking(true); return $client; } );
这里我们发现了2个关键点
- 您提供给
setArguments()
的参数会被转发到工厂 - 容器会尽可能解析类型提示的工厂参数。
标签
标签允许您将自定义字符串标签应用于服务,让您可以使用tagged()
方法收集应用了特定标签的所有服务。
假设您有一系列任务对象,每个都实现了如下TaskInterface
<?php // tasks.php interface TaskInterface { public function execute(): void; } class EmailTask implements TaskInterface { public function execute(): void { // ... send an email } } class NotificationTask implements TaskInterface { public function execute(): void { // ... send push notification } } class LogTask implements TaskInterface { public function execute(): void { // ... write log data } }
作为开发者,我们可以看到这些类是相关的,并且每个都符合TaskInterface
。对每个调用execute()
会运行相应的逻辑。然而,容器对此关系却视而不见。
为了将它们分组,我们可以使用标签
<?php use Swiftly\Dependency\Container; $container = new Container(); $container->register(EmailTask::class) ->setTags(['task']); $container->register(NotificationTask::class) ->setTags(['task']); $container->register(LogTask::class) ->setTags(['task']); foreach ($container->tagged('task') as $task) { $task->execute(); }
这里我们做了一些事情
- 我们将电子邮件、通知和日志任务类注册到了容器中
- 为每个类添加了自定义的
task
标签 - 调用了
tagged()
方法,该方法返回所有具有给定标签的类
如果您对类型安全感兴趣,也可以将类型约束作为第二个参数传递,这样您可以确保所有带标签的服务都是给定类(或实现了给定接口)。这还带来了一些好处,比如可以被您的IDE用于自动完成,以及像Psalm或PHPStan这样的静态分析工具检测到。
<?php foreach ($container->tagged('task', TaskInterface::class) as $task) { // IDE and static analysis can now infer type of `$task` $task->execute(); }
如果返回的服务中的任何一个不匹配TaskInterface
,将抛出一个异常并解释原因。