Инъекция зависимости Yii2, конфигурация и наследование - PullRequest
0 голосов
/ 25 мая 2018

Допустим, у меня есть такая конфигурация (фрагмент из официального руководства ):

$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. Я хочу сохранить код DRY
  2. Thisэто упрощенный пример моих потребностей - в реальном мире вы не ожидаете иметь несколько дочерних классов LinkPager, но это весьма вероятно для других объектов

Мой вопрос: есть лифреймворк способ достижения этого?Решения, которые я придумал:

  1. Взломать собственный __construct в FancyLinkPager (или другой промежуточный класс или черту), чтобы он посмотрел на конфигурацию приложения и вызвал Yii :: configure наэкземпляр, но я не нахожу хороший способ сделать это универсальным способом
  2. Вставить зависимость в FancyLinkPager с помощью объекта «setup», такого как LinkPagerSettings, и настроить этот класс в разделе контейнера моей конфигурации, но это будетСложно поработать и с экземплярами vanilla LinkPager

Возможно, единственное реальное решение - создать собственную реализацию yii \ di \ Container, которая позволяет наследовать конфигурацию от родительских классов, но до того, как я нырнув этом я хотел бы знать, если я не пропустил что-то.

1 Ответ

0 голосов
/ 26 мая 2018

Наконец, я придумал свою собственную реализацию контейнера 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!

Iтакже учли комментарий qiangxue о явной установке значений свойств по умолчанию в определении класса, поэтому, если мы объявим класс 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...