18

我们正在考虑将依赖注入容器集成到我们的项目中。我看过的每个 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());
    }

}

有理由不这样做吗?如果我们走这条路,我是否错过了一些不起作用的 Pimple 魔法?

4

5 回答 5

10

Pimple 中扩展的“魔力”ArrayAccess在于它是完全可重用和可互操作的。Pimple 作为 DIC 的一大特点是定义的服务可以利用先前定义的服务和/或参数。假设(无论出于何种原因)您有一个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 对象的实例化中使用先前注册的“过滤器”服务。这种好处并不是实现 DIC 所独有ArrayAccess的,但可重用性对于代码重用和共享非常有用。您当然可以为某些服务或所有服务硬编码 getter/setter,但可重用性的好处几乎消失了。

另一种选择是使用魔术方法作为 getter/setter。这将为 DIC 提供一个更像您在代码中想要的 API,您甚至可以将它们用作 PimpleArrayAccess代码的包装器(尽管此时您最好编写一个专门构建的 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;
}

您还可以使用__setand__get提供对 DIC 中服务和参数的类似对象访问,如下所示:(仍然包装 Pimple 的ArrayAccess方法)

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

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

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

于 2013-02-13T00:05:45.593 回答
6

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

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

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


旁注:什么是“理想”的方式?依赖注入。

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

于 2014-05-19T07:42:58.440 回答
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"
于 2013-02-13T00:18:29.043 回答
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一些编辑器允许您使用and来提供 doc-block 提示来弥补这一点@method,我相信。

于 2013-02-12T23:46:30.413 回答
0

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

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

例如

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;
        };
    }

}
于 2013-02-26T15:02:36.777 回答