David2m / Syringe
一个功能丰富的PHP依赖注入器,使用起来简单易懂。
Requires
- php: >=5.4.0
Requires (Dev)
- phpunit/phpunit: ^4.5
This package is not auto-updated.
Last update: 2024-09-14 18:58:37 UTC
README
一个具有以下功能的依赖注入容器:
- 自动装配。
- 将对象创建委托给工厂。
- 处理同一类的多个实例。
- 在实例化后调用方法。
- 将接口和抽象类映射到具体实现。
- 循环依赖检测。
- 等等。
目录
- 要求
- 安装
- 实例化对象
- 无法解析的参数
- 设置参数
- 映射到具体实现
- 实例化后调用方法
- 使用工厂(委托实例化)
- 同一类的多个实例
- 共享实例
- 调用方法或函数
要求
Syringe 需要 PHP 5.4 或更高版本。
安装
Composer
composer require david2m/syringe
实例化对象
为了简化(这将是第一个示例),假设我们想实例化一个没有任何依赖的对象
class UserMapper { }
$mapper = $injector->make('UserMapper');
递归解析参数
class PdoAdapter { }
UserMapper
现在依赖于 PdoAdapter
// UserMapper.php public function __construct(PdoAdapter $pdoAdapter) { }
$mapper = $injector->make('UserMapper');
当创建 UserMapper
时,注入器发现它依赖于 PdoAdapter
,因此它首先创建一个 PdoAdapter
对象,并将其注入到 UserMapper
中。
无法解析的参数
有两种情况无法自动解析参数
设置参数
class PdoAdapter { public function __construct($host, $user, $password, $schema) { } }
$constructor = $injector->getConstructor('PdoAdapter'); // Setting one argument $constructor->setArgument('host', 'localhost'); // Setting multiple arguments $constructor->addArguments([ 'host' => 'localhost', 'user' => 'david' ]);
您可以设置任何方法的参数,而不仅仅是构造函数。
$injector ->getMethod('PdoAdapter', 'someMethod') ->setArgument('name', 'value');
您还可以动态设置参数
$injector->make('PdoAdapter', [ 'host' => 'localhost', 'user' => 'david' ]);
动态设置的参数始终优先于在调用 make()
之前设置的参数。
可调用参数
可调用参数会被调用,并且它们的返回值会被传递到方法中。
$injector ->getConstructor('PdoAdapter') ->setArgument('user', function() { return 'bob'; });
如果参数的类型提示是可调用的,并且设置的参数也是可调用的,则参数在传递到方法之前不会被执行。
将字符串参数解析为对象
您可以为具有类或接口类型提示的参数设置类名参数。
class UserMapper { public function __construct(PdoAdapter $pdoAdapter) { } }
$injector ->getConstructor('UserMapper') ->setArgument('pdoAdapter', 'PdoAdapter');
当注入器解析参数时,它会注意到它是一个字符串,但类型提示是一个类。然后,它会将字符串参数(类名)解析为对象。
当需要使用同一类的多个实例时,这种技术特别强大。假设您有两个数据库,一个是本地数据库,另一个是远程数据库,显然它们有不同的连接细节。
class UserMapper { public function __construct(PdoAdapter $pdoAdapter) { } }
class Logger { public function __construct(PdoAdapter $pdoAdapter) { } }
这两个对象都需要不同的 PdoAdapter
实例,一个可以连接到本地数据库,另一个可以连接到远程数据库。
$injector ->getConstructor('PdoAdapter') ->addArguments([ 'host' => 'localhost', 'user' => 'local_user', 'password' => 'local_password', 'schema' => 'local_database_name' ]); $injector ->getConstructor('PdoAdapter#remote') ->addArguments([ 'host' => '103.243.0.78', 'user' => 'remote_user', 'password' => 'remote_password', 'schema' => 'remote_database_name' ]); $injector ->getConstructor('Logger') ->setArgument('pdoAdapter', 'PdoAdapter#remote'); $mapper = $injector->make('UserMapper'); $logger = $injector->make('Logger');
UserMapper
将获取 PdoAdapter
的 #default
实例,而 Logger
将获取 PdoAdapter
的 #remote
实例。有关处理多个实例的更多信息,请点击 此处。
映射到具体实现
interface DatabaseAdapterInterface { }
class PdoAdapter implements DatabaseAdapterInterface { }
// UserMapper.php public function __construct(DatabaseAdapterInterface $dbAdapter) { }
尝试将 UserMapper
作为注入器时,注入器将抛出 InjectorException
异常,因为它无法将 DatabaseAdapterInterface
解析为具体实现。为防止这种情况,您只需将接口映射到具体实现即可,即实现该接口的类。
您还可以将抽象类映射到具体实现。
$injector->setMapping('DatabaseAdapterInterface', 'PdoAdapter'); // You can also add multiple mappings at once $injector->addMappings([ 'DatabaseAdapterInterface' => 'PdoAdapter', 'AbstractClassName' => 'AnotherConcreteImplementation' ]);
实例化后调用方法
// PdoAdapter.php public function connect() { }
$injector ->getMethod('PdoAdapter', 'connect') ->addCall(); $pdoAdapter = $injector->make('PdoAdapter');
一旦 PdoAdapter
被实例化,在返回对象之前将调用 connect()
方法。如果被调用的方法有任何参数,它们将被解析并传递到该方法中。如果方法有任何 无法解析的参数,您必须在创建对象之前 设置它们,或者将它们传递给 addCall()
方法。
使用工厂(委托实例化)
工厂由一个正则表达式和一个可调用的函数组成。正则表达式与正在创建的类的名称进行匹配,如果存在匹配项,则将对象的实例化委托给工厂。
如果您要匹配的类名中包含反斜杠,您不需要转义它们,注入器会自动完成这项工作。
// The regular expression ^PdoAdapter$ will match the classname PdoAdapter $injector->setFactory('^PdoAdapter$', function() { return new PdoAdapter('localhost', 'david', 'mypassword', 'my_database_name'); }); $pdoAdapter = $injector->make('PdoAdapter'); // Factory instantiates the object.
高级工厂使用
假设您在应用程序中有些服务位于 Service
命名空间中。由于某种原因,您不希望注入器实例化任何服务,而希望将该任务委托给您的自己的 ServiceFactory
。
namespace Service; class Recognition { } class Shopping { }
class ServiceFactory { public function create($className) { // Instantiate and return the object. } }
$injector->setFactory('^Service\\', function($className, ServiceFactory $serviceFactory) { return $serviceFactory->create($className); }); $recognition = $injector->make('Service\Recognition'); $shopping = $injector->make('Service\Shopping');
任何以 Service\
开头的类名的实例化都委托给可调用的工厂。如果可调用的工厂有一个名为 $className
的参数,则注入器会将匹配工厂正则表达式的类的全名传递给该参数。
同一类的多个实例
到目前为止,我们只处理了同一类的单个实例。有时您可能需要同一类的多个实例。这种用法的一个例子是处理多个数据库。您的应用程序可能需要连接到本地数据库和远程数据库。
在创建对象、设置其参数或添加其方法调用时,您可以通过在类名后放置一个 #instance-name
来指定您所引用的对象的实例。如果您不提供实例名称,则您正在处理类的 #default
实例。
$injector->make('PdoAdapter'); // Is the same as $injector->make('PdoAdapter#default');
同一类的两个不同实例
$injector ->getConstructor('PdoAdapter') ->addArguments([ 'host' => 'localhost', 'user' => 'local_user', 'password' => 'local_password', 'schema' => 'local_database_name' ]); $injector ->getConstructor('PdoAdapter#remote') ->addArguments([ 'host' => '103.243.0.78', 'user' => 'remote_user', 'password' => 'remote_password', 'schema' => 'remote_database_name' ]); $localPdoAdapter = $injector->make('PdoAdapter'); $remotePdoAdapter = $injector->make('PdoAdapter#remote'); var_dump($localPdoAdapter === $remotePdoAdapter); // bool(false)
共享实例
默认情况下,如果对象由注入器创建,则它会被存储并每次需要该对象实例时都会使用。
$userOne = $injector->make('Entity\User'); $userTwo = $injector->make('Entity\User'); var_dump($userOne === $userTwo); // bool(true)
您可以告诉注入器始终创建对象的新实例
$injector->singleton('Entity\User', false); $userOne = $injector->make('Entity\User'); $userTwo = $injector->make('Entity\User'); var_dump($userOne === $userTwo); // bool(false)
共享对象
$injector->share([$pdoAdapter]);
共享对象并指定实例名称
$injector->share(['remote' => $remotePdoAdapter]); // To retrieve the $remotePdoAdapter $remotePdoAdapter = $injector->make('PdoAdapter#remote');
调用方法或函数
invoke()
方法接受任何有效的 PHP 可调用 任何有效 PHP 可调用和可选的第二个参数,该参数包含您希望在调用方法/函数时传递给它的参数。