本文档描述了导致Container PSR的过程和讨论。其目标是解释每个决定背后的原因。
那里有许多依赖注入容器,这些DI容器具有非常不同的存储条目的方式。
因此,当您从大局出发时,可以通过大量方法解决DI问题,从而实现大量不同的实现。但是,所有的DI容器都满足了相同的需求:它们为应用程序提供了一种检索一组配置对象(通常是服务)的方法。
通过标准化从容器中获取条目的方式,使用Container PSR的框架和库可以与任何兼容的容器一起使用。这将允许最终用户根据自己的喜好选择自己的容器。
Container PSR设定的目标是标准化框架和库如何利用容器来获取对象和参数。
区分容器的两种用法很重要:
大多数时候,这两方并没有被同一方使用。虽然通常是最终用户倾向于配置条目,但通常是获取条目来构建应用程序的框架。
这就是为什么此接口仅关注如何从容器中获取条目的原因。
如何在容器中设置条目以及如何配置它们超出了此PSR的范围。这就是使容器实现独特的原因。一些容器根本没有配置(它们依赖于自动装配),其他容器依赖于通过回调定义的PHP代码,其他容器依赖于配置文件......此标准仅关注如何获取条目。
此外,用于条目的命名约定不是此PSR范围的一部分。实际上,当您查看命名约定时,有两种策略:
这两种策略都有其优点和缺点。这个PSR的目标不是选择一个约定而不是另一个约定。相反,用户可以简单地使用别名来弥合具有不同命名策略的2个容器之间的差距。
PSR声明:
“用户不应该将容器传递给对象,因此对象可以检索自己的依赖关系。这样做的用户使用容器作为服务定位器。通常不鼓励使用服务定位器。“
// This is not OK, you are using the container as a service locator
class BadExample
{
public function __construct(ContainerInterface $container)
{
$this->db = $container->get('db');
}
}
// Instead, please consider injecting directly the dependencies
class GoodExample
{
public function __construct($db)
{
$this->db = $db;
}
}
// You can then use the container to inject the $db object into your $goodExample object.
在BadExample
你不应该注入容器,因为:
BadExample
该类将需要“db”服务。依赖性是隐藏的。通常,ContainerInterface
其他包将使用它。作为使用框架的最终用户PHP开发人员,您不太可能需要直接使用容器或类型提示ContainerInterface
。
将Container PSR用于代码是否被认为是一种好的做法,或者归结为知道您检索的对象是否是引用容器的对象的依赖关系。以下是一些例子:
class RouterExample
{
// ...
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getRoute($request)
{
$controllerName = $this->getContainerEntry($request->getUrl());
// This is OK, the router is finding the matching controller entry, the controller is
// not a dependency of the router
$controller = $this->container->get($controllerName);
// ...
}
}
在此示例中,路由器正在将URL转换为控制器条目名称,然后从容器中提取控制器。控制器实际上不是路由器的依赖项。根据经验,如果您的对象正在计算 可以变化的条目列表中的条目名称,那么您的用例肯定是合法的。
作为例外,工厂对象的唯一目的是创建和返回新实例,可以使用服务定位器模式。然后工厂必须实现一个接口,以便它本身可以被另一个使用相同接口的工厂替换。
// ok: a factory interface + implementation to create an object
interface FactoryInterface
{
public function newInstance();
}
class ExampleFactory implements FactoryInterface
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function newInstance()
{
return new Example($this->container->get('db'));
}
}
在将Container PSR提交给PHP-FIG之前,ContainerInterface
首先在名为container-interop的项目中提出了这个问题。该项目的目标是为实施ContainerInterface
容器PSR 提供试验平台,并为其铺平道路。
在本元文档的其余部分中,您将看到频繁引用
container-interop.
接口名称与讨论的名称相同container-interop
(仅更改名称空间以匹配其他PSR)。它已经在container-interop
[4]中得到了彻底的讨论,并通过投票决定[5]。
与各自投票一起考虑的选项清单如下:
ContainerInterface
:+8ProviderInterface
:+2LocatorInterface
:0ReadableContainerInterface
:-5ServiceLocatorInterface
:-6ObjectFactory
:-6ObjectStore
:-8ConsumerInterface
:-9在对现有容器进行统计分析之后,选择接口将包含哪些方法。[6]。
分析摘要表明:
get()
get()
方法有1个类型为string的必需参数get()
,但它在容器之间没有相同的用途has()
has()
的容器,该方法只有1个字符串类型的参数get()
ArrayAccess
在容器 - 互操作项目[4]的最开始讨论了是否包含定义条目的方法的问题。已经判断出这些方法不属于此处描述的界面,因为它超出了其范围(参见“目标”部分)。
因此,ContainerInterface
包含两种方法:
get()
,使用一个必需的字符串参数返回任何内容。如果找不到条目,则应抛出异常。has()
,返回一个布尔值,带有一个必需的字符串参数。虽然ContainerInterface
只定义了一个必需参数get()
,但它与具有其他可选参数的现有容器不兼容。PHP允许实现提供更多参数,只要它们是可选的,因为实现确实满足接口。
与container-interop的区别:container-interop规范声明:
虽然
ContainerInterface
只定义了一个必需参数get()
,但实现可以接受其他可选参数。
这句话已从PSR-11中删除,因为:
但是,某些实现具有额外的可选参数; 这在技术上是合法的。这些实现与PSR-11兼容。[11]
的类型的$id
参数get()
和has()
已在容器互操作项目进行了讨论。
虽然string
在所有分析的容器中使用,但有人建议允许任何东西(例如对象)允许容器提供更高级的查询API。
给出的示例是将容器用作对象构建器。该$id
参数将是描述如何创建实例的对象。
讨论[7]的结论是,这超出了从容器中获取条目而不知道容器如何提供它们的范围,并且它更适合工厂。
该PSR提供了2个接口,旨在通过容器异常实现。
这Psr\Container\ContainerExceptionInterface
是基础接口。它应该由容器直接抛出的自定义异常来实现。
预计作为容器域的一部分的任何异常都会实现ContainerExceptionInterface
。几个例子:
InvalidFileException
执行该实现ContainerExceptionInterface
。CyclicDependencyException
实现ContainerExceptionInterface
。但是,如果某些代码从容器的作用域中抛出异常(例如,在实例化条目时抛出异常),则不需要容器将此异常包装在实现该条目的自定义异常中ContainerExceptionInterface
。
基本异常接口的用处受到了质疑:它通常不会被捕获[8]。
但是,大多数PHP-FIG成员认为这是最佳实践。基本异常接口在以前的PSR和几个成员项目中实现。因此保留了基本异常接口。
get
使用不存在的id 调用该方法必须抛出实现该异常的异常Psr\Container\NotFoundExceptionInterface
。
对于给定的标识符:
has
方法返回false
,那么get
方法必须抛出一个Psr\Container\NotFoundExceptionInterface
。has
方法返回true
,这并不意味着该get
方法将成功并且不会抛出异常。Psr\Container\NotFoundExceptionInterface
如果缺少所请求条目的一个依赖项,它甚至可以抛出一个。因此,当用户捕获时Psr\Container\NotFoundExceptionInterface
,它有两种可能的含义[9]:
然而,用户可以容易地区分呼叫has
。
在伪代码中:
if (!$container->has($id)) {
// The requested instance does not exist
return;
}
try {
$entry = $container->get($id);
} catch (NotFoundExceptionInterface $e) {
// Since the requested entry DOES exist, a NotFoundExceptionInterface means that the container is misconfigured and a dependency is missing.
}
在撰写本文时,以下项目已经实现和/或使用了container-interop
该接口的版本。
此列表并不全面,仅应作为一个示例,表明对PSR有相当大的兴趣。
这里列出了所有参与讨论或投票的人(在容器互操作和迁移到PSR-11期间),按字母顺序排列: