Пример использования \yii\di\Container

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Пример использования \yii\di\Container

Сообщение pistol »

Прочитал пару статей по \yii\di\Container в YII2. Обе оказались весьма посредственными, рабочей простой реализации не нашел. Буду благодарен, если кто-то поможет разобраться.

Итак, есть у меня интерфейс CartService и ElementService. Есть компонент Cart. Пользователь может передавать в Cart свои реализации CartService и ElementService, с которыми Cart будет работать.

Cart я инициирую в конфиге приложения, передаю в качестве свойств нужные мне реализации

'elementService' = ['class' => 'app\models\Element'],
'cartService' = ['class' => 'app\models\Cart'],

Дальше, возможно, туплю. В init Cart делают такое:
$this->_di = new Container;

//Регистрация зависимости от интерфейсов
$this->_di->set('blabla\CartService', $this->cartService['class']);
$this->_di->set('blabla\ElementService', $this->elementService['class']);

//Регистрация алиаса
$this->_di->set('cart', $this->cartService);
$this->_di->set('element', $this->cartService);
Однако, не понимаю, зачем это нужно. Даже если я передам какую-то дичь, а не имплементирующий CartService класс, никакой ошибки не будет. Зачем я тогда регистрирую зависимость от интерфейса, почему он не проверяет на этот интерфейс при сете?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

Код: Выделить всё

Yii::$container->set(CartService::class, MyCartServiceImplementation::class);
Yii::$container->set(ElementService::class, MyElementServiceImplementation::class);
Yii::$container->set(Cart::class, Cart::class); // раз мы cart прописываем как компонент, то вероятно, это делать не надо - надо затестить

$cart = Yii::$container->get(Cart::class); //см коммент выше     
имхо как-то так, судя по доке.
Да, засеттить мы можем что угодно, но это что угодно не заинжектится в конструктор Cart, поскольку там у тебя должен быть тайпхинтинг на твои интерфейсы - __construct(CartService $cartService, ElementService $elementService).

CartService::class в качестве id мы указываем, если имплементация подразумевается одна, а алиасы, если несколько ('CartServiceFromDb', 'CartServiceFromRedis').
Однако, не понимаю, зачем это нужно. Даже если я передам какую-то дичь, а не имплементирующий CartService класс, никакой ошибки не будет. Зачем я тогда регистрирую зависимость от интерфейса, почему он не проверяет на этот интерфейс при сете?
надо понимать, что CartService::class (который кстати работает только в 5.5+ - учти) - это просто строковый идентификатор сервиса в контейнере, который по соглашению равен полному имени интерфейса. Просто это удобно для автоматического инджекта в зависимый от него сервис - мы смотрим какой интерфейс прописан в тайпхинтинге и извлекаем имплементацию одноименного интерфейса. То есть имплементация не проверяется на соответсвие интерфейсу.
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

Спасибо, перевариваю. Из фундаментального вот это вот непонятно:

__construct(CartService $cartService, ElementService $elementService)

Где мне указать эти зависимости, если я подключаю компонент в конфиге так:
'components' => [
'cart' => [
'class' => 'blabla\Cart',
],
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение ElisDN »

Код: Выделить всё

class Cart extends Component
{
    private $cartService;
    private $elementService;

    public function __construct(CartService $cartService, ElementService $elementService, $config = [])
    {
        $this->cartService = $cartService;
        $this->elementService = $elementService;
        parent::__construct($config);
    }
    ...
} 
И всё. Как только запросите Yii::$app->cart фреймворк сам распарсит этот конструктор и подтянет в него все эти зависимости.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

pistol писал(а):Спасибо, перевариваю. Из фундаментального вот это вот непонятно:

__construct(CartService $cartService, ElementService $elementService)

Где мне указать эти зависимости, если я подключаю компонент в конфиге так:
'components' => [
'cart' => [
'class' => 'blabla\Cart',
],
да, зависимости ресолвятся не внутри класса, а инджектятся снаружи - это называется inversion of control (инверсия контроля). Ресолвя их внутри ты опять же завязываешься на реализацию чего-то например йиишного контейнера, а твой Cart вообще ничгео не должен знать о том, что снаружи. Он говорит: "хочу вот реализации таких интерфейсов", и что-то снаружи их дает.
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

Он говорит: "хочу вот реализации таких интерфейсов", и что-то снаружи их дает.
А если не дает, то что я упустил? "Cannot instantiate interface pistol88\cart\interfaces\CartService" (хотя, интерфейс существует)

Код: Выделить всё

class Cart extends Component
{
    public function __construct(interfaces\CartService $cartService, interfaces\ElementService $elementService) {
    
    }
 
В конфиге:

Код: Выделить всё

        'cart' => [
            'class' => 'pistol88\cart\Cart',
        ], 
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

весь необходимый код покажите - прописывание в контейнере и весь конструктор. Интерфейс и не должен инстанциироваться - вместо него должна инстанциироваться реализация, прописанная в контейнере.
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

прописывание в контейнере
Вот, недостающий пазл. Само прописывание должно быть в Bootstrap модуля, вспомнил из доки.
Теперь я понял, как это работает. Спасибо за терпение, Зеленин и ЭлисДН :)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

pistol писал(а):Само прописывание должно быть в Bootstrap модуля
ну на какой-то стадии ДО инстанциирования Cart мы должны в контейнер прописать. В вашем случае это логично в конфиге, поскольку di мы внедряем для СТОРОННИХ реализаций.
В итоге-то работает?
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

zelenin писал(а):
pistol писал(а):Само прописывание должно быть в Bootstrap модуля
ну на какой-то стадии ДО инстанциирования Cart мы должны в контейнер прописать. В вашем случае это логично в конфиге, поскольку di мы внедряем для СТОРОННИХ реализаций.
В итоге-то работает?
А как в конфиге это сделать? В мануале сказано, что во входном файле своего апликейшена и в Bootstrap распространяемого модуля надо прописывать.

На сегодня уже нет времени, завтра продолжу пилить. Сейчас хотябы понял суть, завтра-послезавтра запилю и выложу решение.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

pistol писал(а):А как в конфиге это сделать?
просто голым кодом:

Код: Выделить всё

Yii::$container->.....;

// дальше конфиг
return [
...
];
 
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

zelenin, круто.

Для чудачков, которые нашли тему по поиску и пытаются тоже вникнуть, - вся суть DI в этом:
зависимости ресолвятся не внутри класса, а инджектятся снаружи - это называется inversion of control (инверсия контроля)
Как только запросите Yii::$app->cart фреймворк сам распарсит этот конструктор и подтянет в него все эти зависимости.
Внимательно прочитаете это - поймете, как работает. Само прописывание (Yii::$container->set) - на какой-то стадии до инстанциирования Cart (конфиг, бутстрап, входной файл).
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

но вообще рекомендую такой подход: 90% будут пользоваться дефолтной реализацией, поэтому не надо их заморачивать лишним кодом - https://github.com/yiisoft/yii2/blob/ma ... injection-
то есть компонент создаем фабрикой, а если проггеру нужно другую реализацию сделать, то он создает другую фабрику и прописывает ее в контейнере.
UPD: при таком подходе нам вообще не нужно сеттить реализации интерфейсов - только фабрику. Первый подход популярен в симфони, смесь второго и первого юзается в зенд экспрессив (фабрики с получением зависимостей из контейнера).
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

Вот так все работает:

В Bootstrap:

Код: Выделить всё

        yii::$container->set(CartService::class, Cart::class);
        yii::$container->set(ElementService::class, CartElement::class);
В Cart:

Код: Выделить всё

    public function __construct(interfaces\CartService $cartService, interfaces\ElementService $elementService)
    {
        $this->_cart = $cartService->my();
        $this->_element = $elementService;
    }
В config:

Код: Выделить всё

    'components' => [
        'cart' => [
            'class' => 'pistol88\cart\Cart',
        ],
А вот так (при попытке настроить currency в Cart) уже не работает:

Код: Выделить всё

    'components' => [
        'cart' => [
            'class' => 'pistol88\cart\Cart',
            'currency' => 'руб.',
        ],
Argument 2 passed to pistol88\cart\Cart::__construct() must implement interface pistol88\cart\interfaces\ElementService, array given
Что я упустил?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение ElisDN »

pistol писал(а):Что я упустил?

Код: Выделить всё

parent::__construct($config); 
Аватара пользователя
pistol
Сообщения: 216
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Пример использования \yii\di\Container

Сообщение pistol »

Всем спасибо, получилось.

Установил зависимости: https://github.com/pistol88/yii2-cart/b ... tstrap.php
В конструктор компонента эти зависимости передаются автоматически: https://github.com/pistol88/yii2-cart/b ... r/Cart.php

Буду причесывать сейчас.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

CartService::class - 5.5+ (выше упоминал). Либо переделайте на 'pistol88\cart\interfaces\CartService' или в composer.json повысьте версию
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Пример использования \yii\di\Container

Сообщение zelenin »

$_ - выглядит лоховато. Нет смысла использовать подчеркивание в приватных свойствах и методах.
Ответить