David2m/Syringe

一个功能丰富的PHP依赖注入器,使用起来简单易懂。

v1.0.0 2016-08-11 12:23 UTC

This package is not auto-updated.

Last update: 2024-09-14 18:58:37 UTC


README

一个具有以下功能的依赖注入容器:

  1. 自动装配。
  2. 将对象创建委托给工厂。
  3. 处理同一类的多个实例。
  4. 在实例化后调用方法。
  5. 将接口和抽象类映射到具体实现。
  6. 循环依赖检测。
  7. 等等。

目录

- 要求
- 安装
- 实例化对象
- 无法解析的参数
- 设置参数
- 映射到具体实现
- 实例化后调用方法
- 使用工厂(委托实例化)
- 同一类的多个实例
- 共享实例
- 调用方法或函数

要求

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 中。

无法解析的参数

有两种情况无法自动解析参数

  1. 没有类型提示 - 在此情况下,您必须明确告诉注入器参数是什么。请参阅设置参数
  2. 类型提示是接口或抽象类 - 请参阅映射到具体实现

设置参数

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() 之前设置的参数。

这在添加方法调用或使用invoke()方法时非常有用。

可调用参数

可调用参数会被调用,并且它们的返回值会被传递到方法中。

$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 可调用和可选的第二个参数,该参数包含您希望在调用方法/函数时传递给它的参数。