代码之家  ›  专栏  ›  技术社区  ›  SkarabePL

Yii2依赖注入、配置和继承

  •  2
  • SkarabePL  · 技术社区  · 6 年前

    假设我有这样一个配置(来自 official guide ):

    $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(或另一个中间类或特性)中破解一个自定义的_构造,这样它就可以查看应用程序的配置,并在实例上调用Yii::configure,但我没有找到一种通用的方法
    2. 使用“setup”对象将依赖项注入FancyLinkPager,比如LinkPagerSettings,并在我的配置的容器部分配置该类,但使用vanilla LinkPager实例也会带来一些麻烦

    也许唯一真正的解决方案是创建我自己的yii\di\Container实现,允许从父类继承配置,但在深入讨论之前,我想知道我是否忽略了一些东西。

    1 回复  |  直到 6 年前
        1
  •  0
  •   SkarabePL    6 年前

    最后,我提出了自己的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],
        ],
    ],
    

    现在如果我创建一个类 FancyLinkPager 这延伸了 yii\widgets\LinkPager 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();