在工厂类中充分利用DI

时间:2018-03-13 10:24:03

标签: php dependency-injection

假设我有一组应该由工厂实例化的类。该工厂必须向实例化的类注入一个参数。

<?php
class ClassA
{
    public function __constructor(
        \My\Logger $logger,
        \Dependency\For\ClassA $dependencyA2,
        \One\More\Dependency\For\ClassA $dependencyA3,
        ...
        string $parameterInjectedByTheFactory

    ) {
        $this->logger = $logger;
        $this->dependencyA2 = $dependencyA2;
        ...
        $this->parameterInjectedByTheFactory = $parameterInjectedByTheFactory;
    }
}

<?php
class ClassB
{
    public function __constructor(
        \My\Logger $logger,
        \Dependency\For\ClassB $dependencyB2,
        \One\More\Dependency\For\ClassB $dependencyB3,
        ...
        string $parameterInjectedByTheFactory
    ) {
        $this->logger = $logger;
        $this->dependencyB2 = $dependencyB2;
        ...
        $this->parameterInjectedByTheFactory = $parameterInjectedByTheFactory;
    }
}

我觉得下面的课是错的。它不应该知道ClassAClassB的特定依赖关系,不是吗?

class Factory
{
    public function __constructor(
        \My\Logger $logger
    ) {
        $this->logger = $logger;
    }

    public function make($class, $myParameter)
    {
        switch($class)
        {
            case 'ClassA':
                return new $class(
                    $this->logger,
                    new \Dependency\For\ClassA,
                    ...
                    $myParameter
                )            
            case 'ClassB':
                return new $class(
                    $this->logger,
                    new \Dependency\For\ClassB,
                    ...
                    $myParameter
                )
        }
    }
}

我想利用DI自动装配来实例化classA和classB,但在工厂中注入容器是一种不好的做法。

一个更现实的例子。我有一个EndpointManager方法get($endpointCode),它在数组中查找$ endpointCode。此数组条目具有构建端点的信息:

<?php
[
    'endpoint1' => [
        'class' => '\My\Class\For\Dealing\With\MySQL',
        'connectionData' => 'mysql:user@localhost',
    ],

    'endpoint2' => [
        'class' => '\My\Class\For\Dealing\With\REST',
        'connectionData' => [
            'url': 'https://localhost/api/rest/',
            ...
        ],
    ],
]

EndpointManager必须实例化'class'条目中定义的类的对象,并将connectionData传递给它。但\My\Class\For\Dealing\With\MySQL\My\Class\For\Dealing\With\REST的构造函数是异构的,每个构造函数都有自己可能存在的大量依赖关系。有没有办法利用DI来实例化这些对象。

这种情况的最佳方法是什么?

1 个答案:

答案 0 :(得分:0)

您可以将具体类的创建委托给匿名函数,例如

<?php

$dependencyManager>add('endpoint1', function ($di) {
     return new \My\Class\For\Dealing\With\MySQL('mysql:user@localhost', .., ...);
});

$dependencyManager>add('endpoint2', function ($di) {
     return new \My\Class\For\Dealing\With\REST('https://localhost/api/rest/', .., ..., ...);
});

获取实例类:

<?php

$endpoint1Instance = $dependencyManager->get('endpoint1');
$endpoint2Instance = $dependencyManager->get('endpoint2');

其中add()get()方法可以声明如下:

<?php

class DependencyManager 
{
    private $dependencies = [];
    ...

    public function add($key, callable $factory)
    {
          $this->dependencies[$key]['instance'] = null;
          $this->dependencies[$key]['factory'] = $factory;
    }

    public function get($key)
    {
       if (isset($this->dependencies[$key]) {               
           $instance = $this->dependencies[$key]['instance'];
           if (is_null($instance)) {
               $instance = $this->dependencies[$key]['factory']($this);
               $this->dependencies[$key]['instance'] = $instance;
           }
           return  $instance;
       } else {
          throw new Exception('not found');
       }    
    }
    ...
}

您可能希望查看现有的依赖关系管理器实现,例如Pimple