PSR-11: Container interface - PHP-FIG
PSR-11规范了容器接口,其中包括ContainerInterface、ContainerExceptionInterface、NotFoundExceptionInterface,如下所示。
接口
<?php
namespace Psr\Container;
/**
* 容器的接口类,提供了获取容器中对象的方法。
*/
interface ContainerInterface
{
/**
* 在容器中查找并返回实体标识符对应的对象。
*
* @param string $id 查找的实体标识符字符串。
*
* @throws NotFoundExceptionInterface 容器中没有实体标识符对应对象时抛出的异常。
* @throws ContainerExceptionInterface 查找对象过程中发生了其他错误时抛出的异常。
*
* @return mixed 查找到的对象。
*/
public function get($id);
/**
* 如果容器内有标识符对应的内容时,返回 true 。
* 否则,返回 false。
*
* 调用 `has($id)` 方法返回 true,并不意味调用 `get($id)` 不会抛出异常。
* 而只意味着 `get($id)` 方法不会抛出 `NotFoundExceptionInterface` 实现类的异常。
*
* @param string $id 查找的实体标识符字符串。
*
* @return bool
*/
public function has($id);
}
<?php
namespace Psr\Container;
/**
* 容器中的基础异常类。
*/
interface ContainerExceptionInterface
{
}
<?php
namespace Psr\Container;
/**
* 容器中没有查找到对应对象时的异常
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
实现
要实现一个依赖注册容器,首先我们需要实现PSR-11的ContainerInterface接口(其实不实现也OK,但是为了业界统一规范,还是实现一下比较好,这样一来,大家实现的容器就是通用的了),主要的实现逻辑放在get方法中。
在实例化一个对象之前,我们首先关注的是该类的构造方法中有没有参数,参数对象的实例化需不需要依赖其它对象,例如:
<?php
class Person
{
}
class Student
{
public function __construct(Person $person)
{
}
}
$person = new Person();
$student = new Student($person);
以上,传统的情况下,如果我们要实例化一个Student对象,因为Student的构造函数中依赖于Person对象,所有在实例化Student对象之前需要实例化Person对象。那如果Person的构造函数中又依赖其它对象呢,那还得手动new一个依赖对象,这样处理感觉就没那么方便了。
所以我们可以使用依赖注入的方式来实现,将对象的实例化交给容器来处理,让容器自动注入依赖,这样一来就不用手动new依赖对象了。
实现原理:递归判断构造函数是否有依赖参数,依赖参数是否有依赖对象,有依赖则自动注入。
<?php
namespace Di;
use Psr\Container\ContainerInterface;
class Container implements ContainerInterface
{
/**
* 注册树
*
* @var array
*/
protected array $register;
/**
* @throws ReflectionException
*/
public function get(string $class)
{
// 构造函数参数
$constructorParams = [];
// 判断当前类是否已注册
if (isset($this->register[$class])) {
return $this->register[$class];
}
// 反射类
$reflector = new ReflectionClass($class);
// 是否需要处理构造函数
if ($reflector->getConstructor()) {
// 构造函数参数
$params = $reflector->getConstructor()->getParameters();
if (!empty($params)) {
foreach ($params as $param) {
// 反射参数类
$paramReflector = new ReflectionClass($param->getClass()->name);
// 参数类名
$paramClass = $paramReflector->name;
// 递归实例化参数
$constructorParams[] = $this->get($paramClass);
}
}
}
if (!empty($constructorParams)) {
return new $class(...$constructorParams);
}
$object = new $class();
// 更新注册树
$this->register[$class] = $object;
return $object;
}
/**
* @param string $class
* @return bool
*/
public function has(string $class): bool
{
return isset($this->register[$class]);
}
}
注:该容器实现类并未考虑异常情况,可根据自己的业务情况抛异常
使用
<?php
use Di\Container;
class Person
{
public function eat()
{
var_dump('eat');
}
}
$container = new Container();
/**
* @var Person
*/
$person = $container->get(Person:class);
$person->eat()