Service Locator Repository injection with trait

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Сегодня у нас тут светило яркое теплое солнышко, и у меня появились силы помучать абстрактного коня.
Вот что я тут надумал: сеттеры раздражают, инъектить каждый сервис в каждую модель - лень. В конструктор сервисы передать можно не всегда. Например, если расширяю ArrayObject или SplFixedArray - конструктор занят. ServiceLocator с конфигами - overkill, хочу без конфигов.
Придумал хак - инъектить сам контейнер через трейт.
Мелочь, но еще не встречал.

Трейт
PHP:
<?php
/**
* This trait provides implicit injection of a dependency injection container
*/

namespace GK;


use Pimple\Container;

trait DIContainerTrait
{
    /**
     * @var Container
     */
    public static $container;

    /**
     * @return Container
     */
    public static function getContainer()
    {
        if ( static::$container !== NULL ) {
            return static::$container;
        }
        $trait = __TRAIT__;
        return $trait::$container;
    }

    public static function setContainer(Container $c = null)
    {
        self::$container = $c;
    }

}
Модель
PHP:
namespace GK\Entities;

use GK\DIContainer;

class Product
{
    use DIContainerTrait;

    /**
     * Counts the price rounded to tens
     * @return mixed
     */
    public function getPrice() {
        return ceil( $this->getContainer()->CurrencyRate->getRate() * $this->attributes['price_usd'] / 10 ) * 10;
    }
// ...skipped
}
bootstrap
PHP:
$container = new \GK\Container($settings);

GK\DIContainerTrait::setContainer($container);
Расскажите, пожалуйста, почему я [ваш любимый эпитет]. Именно почему, а не кто :)
 
Последнее редактирование:

Redjik

Джедай-мастер
Потому что
a) SL это плохо
б) рендерить вьюху в модели - очень плохо ;)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@Redjik, не понимаю как вычисление текущей цены товара по курсу относится ко view.
пусть будет
PHP:
public function loadById($id) {
    $Select = $this->getContainer()->qbf->newSelect()
            ->cols(['id','brand','name','size'])
            ->from('products p')
            ->where('p.id=?',$id)
            ;
        $res = $this->getContainer()->db->perform($Select);
я понимаю, что плохо все, кроме SimplePHPEasyPlus, но интересно почему
 

Вурдалак

Продвинутый новичок
Ни Product, ни другие VO/Entities (ArrayObject, SplFixedArray) не должны быть сервисами. И, соответственно, контейнер тут ни в каком виде не нужен.

А есть ли разница как именно писать говнокод: SL::$db или $this->getContainer()->db?

я понимаю, что плохо все, кроме SimplePHPEasyPlus, но интересно почему
Что именно «почему»? Почему SL — это плохо? http://lmgtfy.com/?q=service+locator+antipattern
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Насколько я знаю, SL не считается антипаттерном однозначно. Если все зависимости инъектить только явно - здравствуй, SimplePHPEasyPlus.
Мне кажется, что это бесполезная трата большого количества времени во многих случаях.
 
Последнее редактирование:

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@Вурдалак, хорошо, а какой вид инъекции ты предпочитаешь, если не SL? 10 сеттеров ручками в каждой модели пишешь?

А есть ли разница как именно писать говнокод: SL::$db или $this->getContainer()->db?
да, у меня нет статики, нет привязки к имени класса, не задействован namespace, объект инъектится в объект
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Насколько я знаю, SL не считается антипаттерном однозначно. Если все зависимости инъектить только явно - здравствуй, SimplePHPEasyPlus.
Не, не все, обычно я работаю с контейнером напрямую в некоторых инфраструктурных штуках, где требуется lazy loading.

@Вурдалак, хорошо, а какой вид инъекции ты предпочитаешь, если не SL? 10 сеттеров ручками в каждой модели пишешь?
Я вообще не использую сеттеры. Под «моделью» ты можешь понимать что угодно, поэтому я не могу ответить на твой вопрос.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Согласен насчет "модели", давай возьмем класс сущности - там, где хранятся данные и расчет их, например, для товара.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@MiksIr, как-бы да, я о том же, не все считают SL злом.


Через трейт даже можно сделать полноценный Dependency Injection - в трейте объявить __get(), и отдавать сервисы. Писать можно так же, как если бы сервисы инъектнули через сеттеры. Сейчас попробую.
 

AmdY

Пью пиво
Команда форума
Зачем сложности с трейтами, когда можно просто взять готовую реализацию DI и инджектить в конструктор и метод, а в самом сервисе не думать о том, как в него попадут нужные данные.
Когда-то пользовался SL, но после того как появилась масса готовых реализаций DI контейнеров смысла париться об этом вообще не вижу.

p.s. У нас на проекте подобный коневетор вылился в отдельный микросервис, потому что появилась куча правил и частые их изменения. А в микросервисах за каждым getPrice не будешь бегать, данные нужно аккаумулировать и лишь затем делать запрос.
 
Последнее редактирование:

Sufir

Я не волшебник, я только учусь
Зачем сложности с трейтами, когда можно просто взять готовую реализацию DI и инджектить в конструктор и метод, а в самом сервисе не думать о том, как в него попадут нужные данные.
Когда-то пользовался SL, но после того как появилась масса готовых реализаций DI контейнеров смысла париться об этом вообще не вижу.
Это тебе видимо с Yii работать не приходится. Там, что бы SL обойти, приходится прикладывать усилия...

@Sufir, никаких усилий там не надо, это вообще не по теме.
У тебя видимо другой Yii или я не так его готовлю, я вот сейчас пытаюсь полностью избавиться от его использования - пока не получается. Проникает повсюду, в модулях всяких используется и вообще так и просится повсюду. Что-бы заработало приходится костыли даже во втором Yii вот такого плана лепить, что бы в него из контейнера брались зависимости, а полностью их убрать пока не получается:
PHP:
$config['components']['db'] = function () {
        return Yii::$container->get('dbMaster');
};
 
Последнее редактирование:

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@AmdY, я взял Pimple. Вопрос - что дальше с ним делать? Сеттеры писать?
Перечитай первое сообщение :)

@Sufir, никаких усилий там не надо, это вообще не по теме.
 
Последнее редактирование:

AmdY

Пью пиво
Команда форума
@AmdY, я взял Pimple. Вопрос - что дальше с ним делать? Перечитай первое сообщение :)
Это же просто контейнер, вроде поверх него есть реализации DI. ИМХО, лучше брать сразу php-di или аналоги.

offtop
По поводу yii, давече пришлось одним пальце копаться в его исходниках, это что ли инъекция? Или там где-то поверх есть защита?
https://github.com/yiisoft/yii2/blob/master/framework/db/Query.php#L300
 

MiksIr

miksir@home:~$
Через трейт даже можно сделать полноценный Dependency Injection - в трейте объявить __get(), и отдавать сервисы. Писать можно так же, как если бы сервисы инъектнули через сеттеры. Сейчас попробую.
Причем здесь "писать". Мы же с помощью DI не синтаксис решаем. Мы даем возможность инжектировать разные объекты в зависимый объект ну и решаем проблему видимости зависимостей объекта. У тебя таким образом все еще останется сервис-локатор, просто доставляемый через трейт. Привязку к имени класса в случае синглтона ты заменил на привязку к имени трейта, вот и вся разница. Профита никакого - мы точно так же имеем зависимость "имя сервиса в SL === объект" и точно так же карта наших зависимостей размазана по коду. Просто меняется вид обращения к зависимостям в классе. Ну да и раньше ничто не мешало в конструкторе сделать $this->container = SL::getInstance();
 

MiksIr

miksir@home:~$
offtop
По поводу yii, давече пришлось одним пальце копаться в его исходниках, это что ли инъекция? Или там где-то поверх есть защита?
https://github.com/yiisoft/yii2/blob/master/framework/db/Query.php#L300
Там в пхпдоке "Make sure you properly (guide:db-dao#quoting-table-and-column-names) column names in the expression"
Ну и вероятность, что имя поля для COUNT придет извне, мне кажется, мала.
А так да, потенциально.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
лучше брать сразу php-di или аналоги.
http://php-di.org/doc/definition.html - да, конечно,
полноценный DI не всегда нужен, не хочется конфигов, аннотаций, лямбд бесконечных уровней вложенности, на то чтобы трейсом их пройти надо пол-дня

Привязку к имени класса в случае синглтона ты заменил на привязку к имени трейта, вот и вся разница. Ну да и раньше ничто не мешало в конструкторе сделать $this->container = SL::getInstance();
Хорошо написано, надо подумать.
Зависимость "имя сервиса в SL === объект" - очень легко решается массивом соответствий. В любом DI у тебя есть идентификатор для сервиса, который как-то мапится на сервис. Разница лишь в том, вынесен ли массив соответствий в отдельный конфиг, или прописан в самом классе.
"карта наших зависимостей размазана по коду" - наоборот, все сервисы в одном месте, доступны всем сразу.
 

MiksIr

miksir@home:~$
Зависимость "имя сервиса в SL === объект" - очень легко решается массивом соответствий.
Ну не будешь же ты в SL передавать имя "откуда вызвано"? Или бектрейсом определять вызвавшего. А как иначе ты инжектируешь разные сервисы (одного интерфейса) разным потребителям?
"карта наших зависимостей размазана по коду" - наоборот, все сервисы в одном месте, доступны всем сразу.
Не, я не про то. Я про определение, от каких сервисов у тебя зависит твой класс. В случае инъекций через конструктор - достаточно глянуть на конструктор. А если у тебя по всему коду размазано "дай это", "дай то" - уже не явно. Мне этим инжектирование через сеттеры не нравится, но если это делается через контейнер, то в общем все-равно, глядеть уже просто конфиг надо.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Ну не будешь же ты в SL передавать имя "откуда вызвано"? Или бектрейсом определять вызвавшего.
__CLASS__

В случае инъекций через конструктор - достаточно глянуть на конструктор. А если у тебя по всему коду размазано "дай это", "дай то" - уже не явно.
да, всем классам доступны все сервисы, я сейчас переделал - доступны как поля $this->db
 

MiksIr

miksir@home:~$
да, всем классам доступны все сервисы, я сейчас переделал - доступны как поля $this->db
Вот и спрашивается - чуть чуть сложная система, и как ты это тестировать будешь? Мокать все сервисы в локаторе? Ну в общем весело будут.
типа $this->getContainer()->getDb(__CLASS__)?
Ну можно, да, для простых случаев, хотя и как-то криво смотрится интуитивно.

Я вот еще ссылочкой кину http://misko.hevery.com/code-reviewers-guide/
мне в свое время помогла что-то понять
 
Сверху