LINUX.ORG.RU

DiC через Reflection PHP

 , ,


0

2

Доброго времени суток.

Вопрос про рефлексии в PHP. Простыня, нубство.

Есть так называемое веб-приложение в котором есть контроллеры и сервисы.

Вся бизнес-логика вынесена в сервисы (классы или набор классов) которые дергаются контроллером.

На данный момент объект сервиса инстанцируется так:

$service = App::Service('NameService');

В самом App происходит проверка наличия у пользователя прав на использование этого сервиса и в случае успеха возвращает объект.

Каждый сервис тоже может запускать другие сервисы и взаимодействовать с ними. Это происходит по той же схеме.

Так вот. Появилось желание отвязать все контроллеры и сервисы от App и сделать Dependency Injection.

Как я это представляю:

- В конструкторе класса контроллера или сервиса, в параметры через тайпхинт указываем нужные зависимости.

- DiC используя рефлексию просматривает конструктор и получает имена классов вместе с namecpace и потом все нужные объекты передаются в конструктор.

Казалось бы, все здорово. Но за один запрос таких рефлексий может быть больше десятка (да вообще сколько угодно), ведь DiC придется рекурсивно проходить по всем зависимостям всех сервисов участвующих в запросе.

К плюсам такого подхода я отношу ослабление связанности и «утоньшение» контроллеров. Но стоит ли оно того? На сколько такой подход будет замедлять систему? Понятно что в каких то случаях это экономия на спичках, но все таки.

Я понимаю что ресурс не PHPшников, но ЛОР как то роднее.

cast KRoN73



Последнее исправление: schizoid89 (всего исправлений: 3)

Понятно что в каких то случаях это экономия на спичках, но все таки

Т.е. ты хочешь чтобы тут телепатически определили будет ли это критично в твоём случае?

Но стоит ли оно того?

Лично я считаю, что не стоит.

ослабление связанности

А в чём оно заключается? Тот же контейнер просто будет вызываться неявно.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Т.е. ты хочешь чтобы тут телепатически определили будет ли это критично в твоём случае?

Нет, я скорее хотел бы узнать, в каких случаях такой подход оправдан и оправдан ли вообще. Интересует опыт использования подобного.

А в чём оно заключается? Тот же контейнер просто будет вызываться неявно.

Сервис не привязан к конкретной реализации того кто этот самый сервис запускает. Сервис будет либо запущен либо нет и он ничего не будет знать о состоянии других сервисов. Они либо отработают запрос либо не отработают вообще.

В случае с App нужно проверять состояние вызываемого сервиса.

schizoid89
() автор топика
Ответ на: комментарий от schizoid89

Сервис не привязан к конкретной реализации того кто этот самый сервис запускает.

Имеется ввиду что сервис будет представлять из себя некий класс, который не знает ничего откуда берутся другие сервисы. Если он запущен - это уже будет означать что все что ему нужно работает.

schizoid89
() автор топика
Ответ на: комментарий от schizoid89

Имеется ввиду что сервис будет представлять из себя некий класс, который не знает ничего откуда берутся другие сервисы

В случае с App он тоже не знает откуда берутся другие сервисы.

Если он запущен - это уже будет означать что все что ему нужно работает

Точно также как и с App в конструкторе. Если объект создан успешно (конструктор отработал), то всё ок. В случае с рефлексией конструктор по сути не нужен, т.к. используется внешняя инициализация объекта. Поэтому часто инжектят просто в свойства объекта, чтобы не писать банальные конструкторы.

В случае с App нужно проверять состояние вызываемого сервиса

Не нужно. App должен бросать исключение если что-то не так.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

В случае с App он тоже не знает откуда берутся другие сервисы.

Тем не менее, в методах сервиса я хардово вызываю App когда нужен некий другой сервис. А так конструктор при инициализации положит все нужные для работы сервисы в свойства. Ну тут да, я сам честно говоря не уверен что мне все это надо. Но сама идея закралась и не дает покоя.

App должен бросать исключение если что-то не так.

Ну это и сейчас происходит. Но до того как это произойдет, другие сервисы уже могут что то сделать. Либо нужно выявлять доступность всех сервисов еще до начала их запуска.

Возможно я слишком мудрю конечно.

schizoid89
() автор топика
Ответ на: комментарий от schizoid89

А так конструктор при инициализации положит все нужные для работы сервисы в свойства

Ну вместо

public function __construct()
{
    $this->a = App::service('a');
}

будет

public function __construct(A $a)
{
    $this->a = $a;
}

Это те же яйца, вид сбоку, т.к. всё равно нужно писать конструктор с присвоением свойств вручную. То что App::service('a') вызывается где-то извне ничего не меняет.

Но до того как это произойдет, другие сервисы уже могут что то сделать

Это уже другая проблема и DI тут ни при чём. Не нужно пихать работу в конструктор, а только инициализацию (или вообще ничего).

нужно выявлять доступность всех сервисов еще до начала их запуска

Так этим конструктор и занимается. Он должен сделать объект и ничего не «запускать».

Если уж хочется сделать «потоньше», то нужно инжектить в свойства

/** @type A */
public $a;

Тогда конструктор не нужен вообще.

no-such-file ★★★★★
()
Ответ на: комментарий от schizoid89

хардово вызываю App

Но сам алгоритм работы App не хардовый и может меняться, возвращая разные реализации нужных сервисов. Вот если бы ты вызывал new A() - вот это хардово, т.к. имя класса (реализации) хардкодится.

no-such-file ★★★★★
()

существующие реализации DI на PHP используют cache. То есть первый раз или в dev режиме можно юзать рефлексию, а потом генерить весь тот граф зависимостей в php файл и просто юзать.

nguseff
()

Использовать service locator или инжектить в конструктор - дело вкуса и подхода к разработке. В Symfony вот принято считать, что разработчики фреймворка лучше тебя знают, как тебе писать твоё приложение, поэтому достать какой-либо сервис в произвольном месте кода через везде доступный контейнер нельзя.

В Yii тебе доступны оба варианта - делай, как хочешь. Если ты знаешь, что делаешь, то проблемой это быть не должно. Другое дело, если проект пишут много людей и логика начинает размазываться. ViewModel начинает лезть в базу данных и т.п.. Если цель оградить себя и других от подобного, то от сервис локаторов и фасадов лучше избавиться.

nguseff
()
Ответ на: комментарий от nguseff

Вот и я полез смотреть во фреймворки. Symfony давно не щупал, а Yii тем более. В Symfony действительно все огорожено так что нужно использовать то что есть. В Yii я так понял есть что то подобно работающее на рефлексиях. Но что то работы с кешами там не нашел. Может плохо смотрел (копал файлы старого проекта).

Что имею на данный момент.

Проект написан мной одним самостоятельно. Когда то не было и сервисов а были толстые контроллеры. Время шло. Постепенно переписал с использованием плюшек PHP 7.

Когда проект начал разрастаться я принял решение разделить все по сервисам, а контроллеры сделать максимально тонкими.

Сейчас проект еще больше разрастается и вероятность того что над ним будут работать больше одного программиста увеличивается. Сейчас это единая платформа, на которой работает интернет-магазин и система автоматизации торговли. Отсюда все растущее кол-во сервисов и микросервисов, от сервиса «оплаты» чего-либо до сервиса «денег» как таковых. И все бы ничего, но вот тот факт что скоро возможно рук станет больше - меня беспокоит.

Есть очень тонкая и гибкая система правил доступа. В App инициализируется проверка всего и вся на право запуска того или иного действия. Разные группы пользователе имеют разный доступ к разным сервисам. Проект разрастается и хотелось бы не только улучшить качество кода, но и оградиться от выстрелов в ногу. Хоть пока это по сути и монолит, но все таки все больше смотрю на парадигму DDD.

DI пока рассматриваю как вариант отвязки сервисов от ядра, но тормознутость рефлексий как бы говорит мне что это плохая идея, по крайней мере в том виде как я этого захотел. С другой стороны не хотелось бы все усложнять. Но если сегодняшнее усложнение принесет пользу в будущем, я готов пилить. Не хотелось бы начать это пилить через пару лет когда все раздуется еще больше.

Уух, простыня. Прошу прощения.

schizoid89
() автор топика
Ответ на: комментарий от schizoid89

В App инициализируется проверка всего и вся на право запуска того или иного действия.

Ващьпе это в миделвари должно вроде как быть

Проект разрастается
все больше смотрю на парадигму DDD

В правильную сторону смотришь, но освоить DDD с наскоку наверно не получится, пробуй/читай/смотри на лучшие практики

DI пока рассматриваю как вариант отвязки сервисов от ядра, но тормознутость рефлексий как бы говорит мне что это плохая идея

возьми люой популярный di контейнер и не парь мозг, эти проблемы там решены. Вот например http://php-di.org/ ну или другой какой понравиться больше главное что бы он был PSR совместим https://github.com/php-fig/container.

Noob_Linux ★★★★
()
Ответ на: комментарий от Noob_Linux

Ващьпе это в миделвари должно вроде как быть

Сейчас App это такая прослойка между сервисами и ядром. Ядро разбито на множество своих сервисов, в том числе Permission, к которому и обращается App за доступом. Внешние сервисы и контроллеры ничего не знают о сервисах ядра.

schizoid89
() автор топика
Ответ на: комментарий от Noob_Linux

возьми люой популярный di контейнер и не парь мозг, эти проблемы там решены. Вот например http://php-di.org/ ну или другой какой понравиться больше главное что бы он был PSR совместим https://github.com/php-fig/container.

Видел в каком то из готовых DI решение задачи с помощью файлов конфигураций. К каждому сервису или контроллеру привязывается свой файл который возвращает массив с зависимостями.

Пока присматриваюсь и смотрю документации разных решений. Спасибо.

schizoid89
() автор топика
Ответ на: комментарий от schizoid89

Опять же, в таком случае можно вместо классов скармливать рефлексии интерфейсы. Но на сколько это даст больше профита скорости?

schizoid89
() автор топика

А в чем собственно вопрос? В скорости? И эффективности???

Изучай C/C++/Rust и реализуй PECL-расширения для скорости.

Вообще, правила просты: YAGNI, SOLID, Lazy Load, не делать лишних обращений и не делать ничего без явной на то надобности (как одно из следствий)

Суть DI/IoC контейнеров, на мой взгляд в том, чтобы запихнуть в контейнер вида ArrayAccess/ContainerInterface нужные «объекты» с отложенной инициализацией (если это уместно), которые понадобятся потом для конструкции экземпляра класса.

Проблема на мой взгляд в том, что в PHP нет матчинга одноименных функций и статической проверки типов наподобие раннего этапа умной проверки типов как в C++.

Поэтому, возможен такой подход: Передать весь этот контейнер и как-то проверить через type hinting все типы аргументов конструктора... Тут либо один конструктор с параметрами, вызываемый через статические методы-конструкторы, либо как-то делать шустро проверку типов, как тут правильно подсказали кэшированием, но при разработке и тестах делать проверки. Есть вариант проверки ассертами. Или вариант, когда контейнер сам подберет необходимые параметры по рефлексии в конструктор и вызовет его, создав правильный экземпляр класса - это в идеале.

Наконец, это зависит от конкретного случая и выбирая реализацию, нужно помнить о простоте, ясности и понятности, эффективности.

Для вдохновения, рекомендуется перечитать вдумчиво Страуструпа и что нибудь про анти-паттерны.

И еще, для вдохновения - https://en.wikipedia.org/wiki/Exokernel (для App)

anonymous
()
Ответ на: комментарий от anonymous

Для вдохновения, рекомендуется перечитать вдумчиво Страуструпа

Это че, его бредни в последних главах «OOA,OOD,OOM»? Так этой ахинеи в последних изданиях нет уже.

anonymous
()
Ответ на: комментарий от anonymous

Это че, его бредни в последних главах «OOA,OOD,OOM»? Так этой ахинеи в последних изданиях нет уже.

.. творческим людям свойственны странности ... У него много работ интересных и высказываний. Например:

Я люблю, чтобы мой код был элегантным и эффективным. Логика
должна быть достаточно прямолинейной, чтобы ошибкам было трудно
спрятаться; зависимости — минимальными, чтобы упростить
сопровождение; обработка ошибок — полной в соответствии с
выбранной стратегией; а производительность — близкой к
оптимальной, чтобы не искушать людей загрязнять код
беспринципными оптимизациями. Чистый код хорошо решает одну
задачу.

Из последних работ его ничего не читал, лично мне нравится его классический двухтомник по языку, особенно разделы по проектированию во втором томе.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.