PHP依赖注入 - 疙瘩等。 - 为什么使用关联数组与getter?

时间:2013-02-11 13:58:00

标签: php dependency-injection

我们正在考虑将依赖注入容器集成到我们的项目中。我看过的每个DIC都使用关联数组和/或魔术方法。例如,这是来自Pimple页面的示例:

$container['session_storage'] = function ($c) {
    return new $c['session_storage_class']($c['cookie_name']);
};

$container['session'] = function ($c) {
    return new Session($c['session_storage']);
};

这有什么理由吗?我讨厌在我的代码中使用字符串作为除了文字字符串以外的其他内容。你失去了IDE的强大功能(这使得代码难以维护,这是我们试图避免的事情!)。

我的偏好更像是:

class Container {

    function getSessionStorage()
    {
        return new $this->getSessionStorageClass($this->getCookieName);
    }

    function getSession()
    {
        return new Session($this->getSessionStorage());
    }

}

有没有理由不这样做?如果我们走这条路,我是否会错过一些不会起作用的疙瘩?

5 个答案:

答案 0 :(得分:10)

Pimple中ArrayAccess扩展的“神奇之处”在于它完全可重用且可互操作。作为DIC的Pimple的一个重要特征是定义的服务可以利用先前定义的服务和/或参数。让我们说(无论出于何种原因)你有一个Session对象需要一个Filter实例。没有DIC你可以写:

$session = new Session(new Filter);

你可以写下疙瘩:

$pimple['filter'] = function($c) {
    return new Filter;
};
$pimple['session'] = function($c) {
    return new Session($c['filter']);
}

Pimple在Session对象的实例化中使用先前注册的“Filter”服务。对于实现ArrayAccess的DIC,此优势不唯一,但可重用性对于代码重用和共享非常有用。您当然可以为某些服务或所有服务硬编码getter / setter,但可重用性的好处几乎丢失了。

另一个选择是使用魔术方法作为getter / setter。这将使DIC的API更像您在代码中所需的API,甚至可以将它们用作Pimple ArrayAccess代码的包装器(尽管您可能最好在此时编写专用的DIC )。对Pimple现有方法进行包装可能如下所示:

public function __call($method, $args) {
    if("set" === substr($method, 0, 3)) {
        return $this[substr($method, 3)];
    }
    if("get" === substr($method, 0, 3) && isset($args[0])) {
        return $this[substr($method, 3)] = $args[0];
    }
    return null;
}

您还可以使用__set__get对服务进行类似对象的访问。 DIC中的params,如下所示:(仍然包裹着Pimple的ArrayAccess方法)

public function __set($key, $value) {
    return $this[$key] = $value;
}

public function __get($key) {
    return $this[$key];
}

除此之外,您可以完全重写DIC以专门使用魔术方法,并且具有类似对象的API语法而不是实现ArrayAccess,但这应该很容易理解:]

答案 1 :(得分:5)

您关心IDE自动完成,因为您将使用容器作为服务定位器,即您将打电话给您的容器。

理想情况下你不应该这样做。服务定位器模式是一种反模式:您可以从容器中获取它们,而不是注入所需的依赖项(依赖注入)。这意味着您的代码与容器相关联

疙瘩(及其数组访问)并没有真正解决这个问题,所以我没有直接回答你的问题,但我希望它能让它更清晰。


旁注:"理想"是什么?办法?依赖注入。

永远不要使用或调用容器,除非在应用程序的根目录下(例如创建控制器)。始终注入所需的对象(依赖项),而不是注入整个容器。

答案 2 :(得分:1)

Pimple被设计为像数组一样被访问(它实现了ArrayAccess接口)。如果你想要一个类似方法的界面,只需扩展Pimple并使用__call()魔术方法:

class Zit extends Pimple
{
    public function __call($method, array $args)
    {
        $prefix = substr($method, 0, 3);
        $suffix = isset($method[3])
                ? substr($method, 3)
                : NULL;

        if ($prefix === 'get') {
            return $this[$suffix];
        } elseif ($prefix === 'set') {
            $this[$suffix] = isset($args[0])
                           ? $args[0]
                           : NULL;
        }
    }
}

用法:

$zit = new Zit();

// equivalent to $zit['Foo'] = ...
$zit->setFoo(function() {
    return new Foo();
});

// equivalent to ... = $zit['Foo']
$foo = $zit->getFoo();

至于为什么Pimple没有开箱即用这个功能,我不知道。可能只是为了让它尽可能简单。


修改

关于IDE自动完成,它们也不适用于这样的魔术方法。有些编辑允许您使用@property@method来提供文档块提示来弥补这一点。我相信。

答案 3 :(得分:1)

由于您需要高性能并保持可配置性,因此唯一的选择是生成DI容器代码。

简单的选择是准备您需要的方法并编写生成器。像这样的东西(未经测试的代码,仅用于灵感):

$config_file = 'config.ini';
$di_file = 'var/di.php';
if (mtime($config_file) > mtime($di_file) // check if config changed
    || mtime(__FILE__) > mtime($di_file)  // check if generator changed
{ 
    $config = parse_ini_file($config_file, true); // get DI configuration
    ob_start(); // or use fopen($di_file) instead
    echo "<", "?php\n",
        "class DIContainer {\n";
    foreach ($config_file as $service_name => $service) {
        // generate methods you want, use configuration in $service as much as possible
        echo "function create", $service_name, "() {\n",
             "  return new ", $service['class'], "();\n\n";
    }
    echo "}\n";
    file_put_contents($di_file, ob_get_contents());
    ob_end_clean();
}

require($di_file);
$dic = new DIContainer();

用法:

$service = $dic->createSomeService();
// Now you have instance of FooBar when example config is used

示例配置文件:

[SomeService]
class = "FooBar"

[OtherService]
class = "Dummy"

答案 4 :(得分:0)

我们最终采用两种方法的混合方式。

在dic内部管理事物,通过getter检索外部对象。

e.g。

abstract class DicAbstract {

    /**
     * @var \Pimple
     */
    protected $_dic;

    /**
     * Initialise the pimple container
     */
    public function __construct()
    {
        $this->_dic = new \Pimple();

        $this->defineContainer();
    }

    /**
     * Define dependency items
     */
    protected abstract function defineContainer();
}

class Dic extends DicAbstract {

    /**
     * @return \Component\Error\Manager
     */
    public function errorManager()
    {
        return $this->_dic['errorManager'];
    }

    /**
     * @return SomethingElse
     */
    public function somethingElse()
    {
        return $this->_dic['somethingElse'];
    }

    /**
     * Define the container
     */
    protected function defineContainer()
    {
        $this->_dic['errorTypesGeneral'] = function() {
            return new \Component\Error\Type\General();
        };

        $this->_dic['errorTypesSecurity'] = function() {
            return new \Component\Error\Type\Security();
        };

        $this->_dic['errorManager'] = function($dic) {
            $errorManager = new \Component\Error\Manager();

            $errorManager->registerMessages($dic['errorTypesGeneral']);
            $errorManager->registerMessages($dic['errorTypesSecurity']);

            return $errorManager;
        };
    }

}