Внедрение зависимостей или локатор сервисов - Symfony

Я начал изучать код Symfony2, изучил немного маленьких классов, таких как Pimple, и после нескольких часов изучения пришел к странной идее. Лучшее начало для этого - объяснить, как я понимаю несколько терминов, поэтому:

Зависимость. То, что нужно для работы другого, например "двигатель" в "машине".

Контейнер. Объект или класс, который может хранить множество других объектов, таких как «двигатель», «коробка передач» или даже «автомобиль».

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

Service Locator Процесс, в котором объект запрашивает другой объект, например, в автомобиль вставляется наш контейнер, и когда автомобиль необходимо запустить, он требует от контейнера "двигатель", поэтому контейнер возвращает ему "двигатель"

Когда я изучал код Symphony, они начинались с внедрения зависимостей, но через некоторое время я понимаю, что при создании контроллера вводится весь контейнер, а затем вы можете использовать $ this-> get ('serviceName'), чтобы получить его, поэтому он more выглядит как локатор сервисов, который, согласно некоторым статьям, является анти-шаблоном.

Посеешь как это? Эта граница между DI и SL настолько мала, что иногда ее прерывают? Или я что-то неправильно понял? Если я использую DI, нужно ли мне вставлять каждую службу в контроллер, чтобы я знал извне, что использую? Или контроллер может стать в некоторых случаях контейнером?


person M.Svrcek    schedule 10.10.2014    source источник


Ответы (1)


Вы хорошо понимаете DI. И да, Symfony Controller действительно реализует ContainerAwareInterface, и, как вы сказали, выполняет роль локатора сервисов. Но локатор сервисов - это не антипаттерн. Каждый узор имеет свое правильное и неправильное использование.

Более того, Symfony никоим образом не принуждает вас использовать свой Контроллер. Ваш контроллер может быть службой. Черт, это может быть даже функция!

Вот одна из причин, почему Контроллеры реализованы как локаторы сервисов: Производительность.

Давайте откажемся от аналогии с автомобилем и сосредоточимся на реальном случае, с которым вы столкнетесь в 99% проектов: вам нужен CRUD для ресурса. Допустим, вы создаете приложение Todo и вам нужен контроллер RESTfulish для обработки операций CRUD для ресурса Task.

Меньше всего, что вам нужно, - это способ прочитать все задачи и способ добавить новую задачу, для этого вам потребуются два действия: индекс (обычно также называется список) и store (обычно также называется create).

Обычный поток в Symfony будет таким, в псевдокоде:

indexAction -> getDoctrine -> getTaskRepository -> getAllTasks

storeAction -> getFormFactory -> createForm -> bindRequestDataToForm -> getDoctrine -> saveData

Если Контроллер был локатором службы

Индекс Действие

Когда выполняется действие index, только служба, которая будет разрешена из контейнера, будет ManagerRegistry (в данном случае Doctrine). Затем мы попросим его предоставить нам репозиторий задач, и мы будем выполнять с ним свою операцию.

Магазин Действия

Когда выполняется действие store, мы проделаем немного больше работы: попросим контейнер предоставить нам FormFactory, проделаем с ним некоторые операции, а затем попросим его предоставить нам Доктрина и тоже поработайте с ней.

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

Если бы контроллер был обычным сервисом

Посмотрим, что нужно нашему контроллеру. Из приведенного выше раздела видно, что ему нужны FormFactory и Doctrine.

Теперь, когда вы просто хотите вызвать действие index для чтения всех задач из хранилища данных, ваш контроллер должен будет получить экземпляр контейнера. Прежде чем его можно будет создать, контейнер должен создать экземпляр своих зависимостей: FormFactory и Doctrine. Затем создайте экземпляр контроллера, вводя в него эти два.

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

Ленивые службы

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

Подведение итогов

Контроллер не обязательно должен быть локатором услуг, но может быть. Создание локатора сервисов может быть немного более производительным и более легким для начальной загрузки, но скрывает зависимости. Кроме того, его немного сложнее протестировать, так как вам нужно будет имитировать контейнер зависимостей. Хотите ли вы сделать свои контроллеры службами, функциями или локаторами служб - это ваш выбор, и Symfony не будет заставлять вас использовать какой-либо из этих способов.

По моему опыту, расширение контроллера Symfony по умолчанию и использование контроллеров в качестве локаторов служб - это нормально, если вы не пишете в них свою бизнес-логику, а вместо этого делегируете все это работа с услугами, которые вы получаете из контейнера. Таким образом, очень маловероятно, что у вас будут ошибки в коде контроллера (поскольку методы обычно состоят из 2–3 строк кода), и вы сможете обойтись без их тестирования.

person Igor Pantović    schedule 10.10.2014
comment
Хороший вопрос. Хороший ответ. Вы можете исключить ненужные службы, ограничившись одним действием на контроллер. Мне нравится использовать гибридный подход, при котором я встраиваю свои сервисы в конструктор контроллера, при этом все еще добавляя контейнер через setContainer, чтобы использовать методы в базовом классе контроллера. 2-3 строчки кода кажутся немного оптимистичными даже для действия тонкого контроллера. - person Cerad; 10.10.2014
comment
Стоит отметить, что разделение контроллера на меньшее подмножество похожих действий или выделение класса для каждого действия также позволит избежать создания ненужных зависимостей, а также обеспечит лучшее соблюдение SRP. - person nCrazed; 04.06.2017