php - 让 DI 容器替换全局 $registry 对象是一种好习惯吗?

标签 php unit-testing dependency-injection

我已经开始重构一个小应用程序来使用一个小的 DI 容器而不是 $注册表::getstuff();在我的类(class)中调用我将它们注入(inject)容器中。

这提出了 2 个问题,

Q1 -> 我扩展 Pimple DI class并创建一个容器,该容器具有特定于每个需要 DI 的对象的依赖项。然后我提供对象 the whole shebang ,并在将 DI 的对象分配给我正在构建的对象的类属性的构造函数中对其进行 decrontruct。

我应该在 new object() 调用中分离对象吗?我只是觉得这样更容易,但看到我现在是一个单人团队,我只想确认我有正确的方法。

Q2 -> 我发现如果我在一些主要类上执行此操作,则我一直传递的 $registry 对象将不再需要,这是使用 DI 的正常结果吗,不再注册?我可能在容器中注入(inject)了一两个单例,但看起来这就是我所需要的,甚至那些也可以很容易地被消除,因为 DI 有一个返回相同对象实例的 share() 属性,有效地消除了需要对于单例。这是摆脱需要注册表/单例的应用程序的方法吗,因为如果是这样,就太简单了。

最佳答案

Q2: 如果您遍历整个 $registry 对象....那么您的 Registry 并不是真正意义上的 Registry(如 Fowler描述了它)。

Registry 或多或少是一个具有 get/set 方法的全局对象(“众所周知”)。 在 PHP 中,Registry 实现的两个常见原型(prototype)是

作为单例

class RegistryAsSingleton
{
    public static function getInstance (){
       //the singleton part
    }

    public function getStuff ()
    {
       //some stuff accessed thanks to the registry
    }
}

到处都是静态方法

class RegistryAsStatic
{
    public static function getStuff()
    {
    }
}

到处传递您的 Registry 使其成为一个对象:一个除了提供对其他对象的引用之外没有更大用途的容器。

您的 DI 容器(使用您在 OP 中建议的 Pimple)本身就是一个注册表:它是众所周知的并且使您能够从任何地方获取组件。

是的,我们可以说您的 DI 容器将通过执行相同的功能来消除注册表的要求和必要性。

BUT(总有一个但是)

Registry are always guilty until proven innocent (Martin Fowler)

如果您使用DI 容器 来替换您的注册表,这可能是错误的。

例如:

//probably a Wrong usage of Registry
class NeedsRegistry
{
    public function asAParameter(Registry $pRegistry)
    {
       //Wrong dependency on registry where dependency is on Connection
       $ct = $pRegistry->getConnection();
    }

    public function asDirectAccess ()
    {
       //same mistake, more obvious as we can't use another component
       $ct = Registry::getInstance()->getConnection();
    }
}

//probably a wrong replacement for Registry using DI Container
class NeedsContainer
{
    public function asAParameter(Container $pRegistry)
    {
       //We are dependent to the container with no needs, 
       //this code should be dependent on Connection
       $ct = $pContainer->getConnection();
    }

    public function asDirectAccess ()
    {
       //should not be dependent on container
       $ct = Container::getInstance()->getConnection();
    }
}

为什么会这样?因为你的代码并没有比以前少依赖,它仍然依赖于一个没有提供明确目标(我们这里可能会想到接口(interface))的组件(注册中心或容器)

注册表模式在某些情况下很有用,因为它是一种定义组件或数据(例如全局配置)的简单且相当便宜的方法。

通过移除依赖项来重构上述示例而不依赖 DI 的方法是:

class WasNeedingARegistry
{
    public function asAParameter (Connection $pConnection)
    {
       $pConnection->doStuff();//The real dependency here, we don't care for 
       //a global registry
    }
}

//the client code would be like
$wasNeedingARegistry = new WasNeedingARegistry();
$wasNeedingARegistry->setConnection($connection);

当然,如果客户端代码不知道连接,这可能是不可能的,这可能是您最初可能结束使用注册表的原因。

现在 DI 开始发挥作用

使用 DI 让我们的生活更美好,因为它会处理依赖关系,并使我们能够在准备好使用的状态下访问依赖关系。

在您的代码中的某处,您将配置您的组件:

$container['connection'] = function ($container) {
    return new Connection('configuration');
};
$container['neededARegistry'] = function ($container) {
    $neededARegistry = new NeededARegistry();
    $neededARegistry->setConnection($container['connection']);
    return $neededARegistry;
};

现在您拥有了重构代码所需的一切:

// probably a better design pattern for using a Registry 
class NeededARegistry
{
    public function setConnection(Connection $pConnection)
    {
       $this->connection = $pConnection;
       return $this;
    }

    public function previouslyAsDirectAccess ()
    {
       $this->connection->doStuff();
    }
}

//and the client code just needs to know about the DI container
$container['neededARegistry']->previouslyAsDirectAccess();

“客户端”代码应尽可能隔离。客户端应该负责并注入(inject)自己的依赖项(通过 set- 方法)。客户端不应该负责处理其依赖项的依赖项。

class WrongClientCode
{
    private $connection;
    public function setConnection(Connection $pConnection)
    {
       $this->connection = $pConnection;
    }

    public function callService ()
    {
       //for the demo we use a factory here
       ServiceFactory::create('SomeId')
                       ->setConnection($this->connection)
                       ->call();
       //here, connection was propagated on the solely 
       // purpose of being passed to the Service
    }
}

class GoodClientCode
{
    private $service;
    public function setService(Service $pService)
    {
       //the only dependency is on Service, no more connection
       $this->service = $pService;
    }

    public function callService ()
    {
       $this->service->setConnection($this->connection)
                     ->call();
    }
}

DI 容器将使用已正确配置其连接的服务配置 GoodClientCode

至于单例方面,是的,它将使您摆脱它们。 希望这有帮助

关于php - 让 DI 容器替换全局 $registry 对象是一种好习惯吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6122214/

相关文章:

php - 使用 PHP 将 MySQL 变量插入 Google map

PHP 监禁任意代码

java - JUnit 集成测试应报告 HTTP 请求/响应详细信息

asp.net-mvc-3 - 模拟 Request.QueryString 用于单元测试并针对 View 进行断言

c# - 如何注册一个参数为 `Func<>` 的类?

c# - 简单注入(inject)器 - 将 url 值注入(inject)到服务构造函数中

php - Pusher 没有收到来自 Laravel 5.2 广播的事件

php - 在页面加载时设置选择值

c# - 如何避免每次测试都重复安排TestFixtures?

asp.net-mvc - 在运行时使用 Unity 属性注入(inject)注入(inject) IPrincipal