【问题标题】:Yii2 dependency injection, configuration and inheritanceYii2 依赖注入、配置和继承
【发布时间】:2018-05-25 04:48:33
【问题描述】:

假设我有这样的配置(来自official guide的sn-p):

$config = [
    // ...
    'container' => [
        'definitions' => [
            'yii\widgets\LinkPager' => ['maxButtonCount' => 5],
        ],
    ],
    // ...
];

我创建了一个名为 FancyLinkPager 的类:

class FancyLinkPager extends \yii\widgets\LinkPager
{
    // ...
}

当我像这样创建 FancyLinkPager 类的对象时(请忽略 $pagination 对象,为了正确起见,它在这里):

$pagination     = \Yii::createObject(Pagination::class);
$linkPager      = \Yii::createObject(['class' => LinkPager::class, 'pagination' => $pagination]);
$fancyLinkPager = \Yii::createObject(['class' => FancyLinkPager::class, 'pagination' => $pagination]);
$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 10 as LinkPager's default

我的问题是我希望 $fancyLinkPager->maxButtonCount 按照配置为 5。我知道我可以在配置中添加另一行或对其进行调整以指定我的自定义类,但这不是我的解决方案,因为:

  1. 我想保持代码干燥
  2. 这是我的需求的一个过分简化的示例 - 在现实世界中,您不希望有多个 LinkPager 的子类,但其他对象很有可能

我的问题是:是否有任何框架支持的方式来实现这一点?我想出的解决方案是:

  1. 在 FancyLinkPager(或另一个中间类或特征)中破解自定义 __construct,以便它查看 App 的配置并在实例上调用 Yii::configure,但我没有找到以通用方式执行此操作的好方法
  2. 使用“设置”对象(如 LinkPagerSettings)将依赖项注入 FancyLinkPager,并在我的配置的容器部分配置该类,但使用普通 LinkPager 实例也会带来一些麻烦

也许唯一真正的解决方案是创建我自己的 yii\di\Container 实现,它允许从父类继承配置,但在我深入研究之前,我想知道我是否没有忽略什么。

【问题讨论】:

  • 有同样的问题:我认为无法配置父类。因此我的配置有一些冗余设置...根据this,覆盖 Container 可能会导致性能问题,因为 .我认为您的构造函数方法是合理的。如果您要意识到这一点,请发布答案。
  • 感谢 @robsch 向我指出 Yii2 的工作人员对此的评论,我自己找不到。这让我明白,如果配置继承是可选的和有意的,那将是最好的。

标签: php inheritance dependency-injection yii2


【解决方案1】:

最后我提出了自己的 DI 容器实现,引入了新的可配置属性:

public $inheritableDefinitions = [];

完整类代码:

<?php

namespace app\di;

/**
 * @inheritdoc
 */
class Container extends \yii\di\Container
{
    /**
     * @var array inheritable object configurations indexed by class name
     */
    public $inheritableDefinitions = [];

    /**
     * @inheritdoc
     *
     * @param string $class
     * @param array $params
     * @param array $config
     *
     * @return object the newly created instance of the specified class
     */
    protected function build($class, $params, $config) {
        $config = $this->mergeInheritedConfiguration($class, $config);

        return parent::build($class, $params, $config);
    }

    /**
     * Merges configuration arrays of parent classes into configuration of newly created instance of the specified class.
     * Properties defined in child class (via configuration or property declarations) will not get overridden.
     *
     * @param string $class
     * @param array $config
     *
     * @return array
     */
    protected function mergeInheritedConfiguration($class, $config) {
        if (empty($this->inheritableDefinitions)) {
            return $config;
        }

        $inheritedConfig = [];
        /** @var \ReflectionClass $reflection */
        list($reflection) = $this->getDependencies($class);

        foreach ($this->inheritableDefinitions as $parentClass => $parentConfig) {
            if ($class === $parentClass) {
                $inheritedConfig = array_merge($inheritedConfig, $parentConfig);
            } else if (is_subclass_of($class, $parentClass)) {
                /** @var \ReflectionClass $parentReflection */
                list($parentReflection) = $this->getDependencies($parentClass);

                // The "@" is necessary because of possible (and wanted) array to string conversions
                $notInheritableProperties = @array_diff_assoc($reflection->getDefaultProperties(),
                    $parentReflection->getDefaultProperties());

                // We don't want to override properties defined specifically in child class
                $parentConfig    = array_diff_key($parentConfig, $notInheritableProperties);
                $inheritedConfig = array_merge($inheritedConfig, $parentConfig);
            }
        }

        return array_merge($inheritedConfig, $config);
    }
}

这是如何使用它来完成问题中描述的 LinkPager 的自定义:

'container'  => [
    'inheritableDefinitions' => [
        'yii\widgets\LinkPager'  => ['maxButtonCount' => 5],
    ],
],

现在,如果我创建一个扩展 yii\widgets\LinkPager 的类 FancyLinkPager,DI 容器将合并默认配置:

$pagination     = \Yii::createObject(Pagination::class);
$linkPager      = \Yii::createObject(['class' => LinkPager::class, 'pagination' => $pagination]);
$fancyLinkPager = \Yii::createObject(['class' => FancyLinkPager::class, 'pagination' => $pagination]);
$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 5 as configured - hurrah!

我还考虑了qiangxue's comment 关于在类定义中显式设置默认属性值,所以如果我们这样声明FancyLinkPager 类:

class FancyLinkPager extends LinkPager
{
    public $maxButtonCount = 18;
}

将遵守属性设置:

$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 18 as declared

要在您的应用程序中交换默认 DI 容器,您必须在入口脚本中的某处显式设置 Yii::$container

Yii::$container = new \app\di\Container();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 2011-12-24
    • 2021-04-16
    • 1970-01-01
    • 2017-05-24
    相关资源
    最近更新 更多