Использование инъекции зависимостей над ларавельными фасадами

Я прочитал ряд источников, которые намекают, что фасад ларавелла в конечном счете существует для удобства и что эти классы вместо этого должны быть введены, чтобы обеспечить свободную связь. Даже у Тейлора Отуэлла есть сообщение, объясняющее, как это сделать. Кажется, я не единственный, кто это задает .

use Redirect; class Example class { public function example() { return Redirect::route("route.name"); } } 

станет

 use Illuminate\Routing\Redirector as Redirect; class Example class { protected $redirect; public function __constructor(Redirect $redirect) { $this->redirect = $redirect } public function example() { return $this->redirect->route("route.name"); } } 

Это прекрасно, за исключением того, что я начинаю обнаруживать, что некоторые конструкторы и методы начинают принимать четыре + параметра.

Поскольку Laravel IoC, кажется, только вводит в конструкторы классов и определенные методы (контроллеры), даже когда у меня есть довольно скудные функции и классы, я нахожу, что конструкторы классов собираются с необходимыми классами, которые затем вводятся в необходимых методов.

Теперь я нахожу, что если я продолжу этот подход, мне понадобится мой собственный контейнер IoC, который, похоже, изобретает колесо, если я использую фреймворк вроде laravel?

Например, я использую службы для управления логикой business / view, а не с контроллерами, связанными с ними, – они просто маршрутизируют представления. Таким образом, контроллер сначала берет соответствующую service , затем parameter в своем URL-адресе. Одна служебная функция также должна проверять значения из формы, поэтому мне нужен Request and Validator . Просто у меня есть четыре параметра.

 // MyServiceInterface is binded using the laravel container use Interfaces\MyServiceInterface; use Illuminate\Http\Request; use Illuminate\Validation\Factory as Validator; ... public function exampleController(MyServiceInterface $my_service, Request $request, Validator $validator, $user_id) { // Call some method in the service to do complex validation $validation = $my_service->doValidation($request, $validator); // Also return the view information $viewinfo = $my_service->getViewInfo($user_id); if ($validation === 'ok') { return view("some_view", ['view_info'=>$viewinfo]); } else { return view("another_view", ['view_info'=>$viewinfo]); } } 

Это единственный пример. На самом деле у многих моих конструкторов уже есть несколько классов, которые вводятся (Модели, Сервисы, Параметры, Фасады). Я начал «выгружать» инъекцию конструктора (когда применимо) к инъекции метода, а классы, вызывающие эти методы, используют свои конструкторы для инъекции зависимостей.

Мне сказали, что более четырех параметров для метода или конструктора классов в качестве эмпирического правила – плохой опыт / запах кода. Однако я не вижу, как вы можете избежать этого, если вы выберете путь впрыскивания ларавельных фасадов.

Неужели я ошибаюсь? Являются ли мои классы / функции недостаточно скудными? Я пропустил пункт контейнера laravels или мне действительно нужно подумать о создании моего собственного контейнера IoC? Некоторые другие ответы, похоже, намекают на то, что контейнер laravel может устранить мою проблему?

Тем не менее, похоже, не существует окончательного консенсуса по этому вопросу …

Это одно из преимуществ инсталляции конструктора – это становится очевидным, когда вы класс много, потому что параметры конструктора слишком велики.

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

Скажем, у вас есть страничный контроллер:

 Class PageController { public function __construct( Request $request, ClientRepositoryInterface $clientrepo, StaffRepositortInterface $staffRepo ) { $this->clientRepository = $clientRepo; //etc etc } public function aboutAction() { $teamMembers = $this->staffRepository->getAll(); //render view } public function allClientsAction() { $clients = $this->clientRepository->getAll(); //render view } public function addClientAction(Request $request, Validator $validator) { $this->clientRepository->createFromArray($request->all() $validator); //do stuff } } 

Это главный кандидат на разделение на два контроллера – ClientController и ClientController .

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

Примером этого является addClientAction – для этого требуется запрос и валидатор, чтобы передать их clientRepostory .

Мы можем изменить фактор, создав новый класс специально для создания клиентов из запросов, таким образом уменьшая наши зависимости и упрощая как контроллер, так и репозиторий:

 //think of a better name! Class ClientCreator { public function __construct(Request $request, validator $validator){} public function getClient(){} public function isValid(){} public function getErrors(){} } 

Наш метод теперь становится следующим:

 public function addClientAction(ClientCreator $creator) { if($creator->isValid()){ $this->clientRepository->add($creator->getClient()); }else{ //handle errors } } 

* Нет жесткого и быстрого правила относительно того, какое количество зависимостей слишком велико. Хорошая новость заключается в том, что если вы создали свое приложение, используя свободную связь, повторный факторинг относительно прост. Я бы гораздо лучше видел конструктор с 6 или 7 зависимостями, чем без параметров, и куча статических вызовов, скрытых во всех методах

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

Что касается решений:

1. Разрешение зависимостей вручную

Один из способов разрешения зависимостей, если вы не хотите это делать. конструкторы или методы инъекции, – это вызвать приложение () напрямую:

 /* @var $email_services App\Contracts\EmailServicesContract $email_services = app('App\Contracts\EmailServicesContract'); 

2. Рефакторинг

Иногда, когда я нахожу, что передаю слишком много сервисов или зависимостей в класс, возможно, я нарушил принцип единой ответственности. В таких случаях может потребоваться перепроектировать, разбив службу или зависимость на более мелкие классы. Я бы использовал другую услугу, чтобы объединить связанную группу классов, чтобы служить чему-то как фасад. По сути, это будет иерархия классов услуг / логики.

Пример. У меня есть служба, которая генерирует рекомендуемые продукты и отправляет их пользователям по электронной почте. Я вызываю службу WeeklyRecommendationServices , и она принимает в качестве другой службы 2 службы – службы Recommendation которые являются черным ящиком для генерации рекомендаций (и у нее есть свои зависимости – возможно, репо для продуктов, помощник или два) и EmailService который, возможно, имеет Mailchimp как зависимость). Некоторые низкоуровневые зависимости, такие как перенаправления, валидаторы и т. Д., Будут в этих дочерних службах вместо службы, которая действует как точка входа.

3. Использовать глобальные функции Laravel

Некоторые из Фасадов доступны как вызовы функций в Laravel 5. Например, вы можете использовать redirect()->back() вместо Redirect::back() , а также view('some_blade) вместо View::make('some_blade') . Я считаю, что это одинаково для dispatch и некоторых других часто используемых фасадов.

(Отредактировано для добавления). 4. Использование признаков. Поскольку я работал над поставленными задачами сегодня, я также заметил, что другой способ встраивания зависимостей – это использование признаков. Например, черта DispathatesJobs в Laravel имеет следующие строки:

  protected function dispatch($job) { return app('Illuminate\Contracts\Bus\Dispatcher')->dispatch($job); } 

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

Методы класса, которые составляют часть механизма маршрутизации в Laravel (промежуточное программное обеспечение, контроллеры и т. Д.), Также имеют свои типы-подсказки, используемые для ввода зависимостей – им не все нужно вводить в конструктор. Это может помочь сохранить ваш конструктор тонким, хотя я не знаком ни с одним из четырех правил ограничения параметров; PSR-2 допускает, что определение метода растягивается на несколько строк, предположительно, потому что нередко требуется более четырех параметров.

В вашем примере вы можете внедрить службы Request and Validator в конструкторе в качестве компромисса, поскольку они часто используются несколькими способами.

Что касается установления консенсуса, то Laravel должен быть более самоуверенным, поскольку приложения должны быть достаточно похожими, чтобы использовать подход, основанный на одном уровне. Более легкий вызов – это то, что я думаю, что фасады будут идти по пути додо в будущей версии.

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

Для примера, который вы дали, я покажу, как я обычно обрабатываю его:

 // MyServiceInterface is binded using the laravel container use Interfaces\MyServiceInterface; use Illuminate\Http\Request; use Illuminate\Validation\Factory as Validator; ... class ExampleController { protected $request; public function __constructor(Request $request) { // Do this if all/most your methods need the Request $this->request = $request; } public function exampleController(MyServiceInterface $my_service, Validator $validator, $user_id) { // I do my validation inside the service I use, // the controller for me is just a funnel for sending the data // and returning response //now I call the service, that handle the "business" //he makes validation and fails if data is not valid //or continues to return the result try { $viewinfo = $my_service->getViewInfo($user_id); return view("some_view", ['view_info'=>$viewinfo]); } catch (ValidationException $ex) { return view("another_view", ['view_info'=>$viewinfo]); } } } class MyService implements MyServiceInterface { protected $validator; public function __constructor(Validator $validator) { $this->validator = $validator; } public function getViewInfo($user_id, $data) { $this->validator->validate($data, $rules); if ($this->validator->fails()) { //this is not the exact syntax, but the idea is to throw an exception //with the errors inside throw new ValidationException($this->validator); } echo "doing stuff here with $data"; return "magic"; } } 

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

Только одно последнее замечание: если вы создаете небольшое приложение или даже страницу в огромном приложении, например, «контактную страницу» и «контактную страницу отправки», вы можете, конечно, сделать все в контроллере с фасадами, это просто зависит от сложность проекта.

Мне нравится laravel из-за его прекрасной архитектуры. Теперь, как с моего подхода, я бы не вводил все фасады в метод контроллера только почему? Injecting Redirect фасады только в неправильной практике контроллера, поскольку это может потребоваться в другом. И главным образом, вещи, которые в основном используются, должны быть объявлены для всех, а для тех, кто использует некоторые или только тогда их наилучшую практику, чтобы вводить их с помощью метода, так как когда вы заявляете, что это будет затруднять оптимизацию вашей памяти, а также скорость вашего код. Надеюсь, это поможет

Не столько ответ, сколько пища для размышлений после разговора с моими коллегами, которые сделали некоторые очень важные моменты;

  1. Если внутренняя структура laravel изменяется между версиями (что произошло в прошлом, видимо, очевидно), инъекция разрешенных путей класса фасадов сломала бы все на обновлении – при использовании стандартных фасадов и вспомогательных методов в основном (если не полностью) избегает этой проблемы ,

  2. Хотя развязка кода, как правило, хорошая вещь, накладные расходы на внедрение этих разрешенных путей класса фасадов заставляют классы засоряться. Для разработчиков, принимающих участие в проекте, больше времени тратится на выполнение кода, который может быть лучше использован для исправления ошибок или тестирования. Новые разработчики должны помнить, какие введенные классы являются разработчиками и которые являются larvels. Разработчики, незнакомые с laravel под капотом, должны тратить время на поиск API. В конечном итоге вероятность появления ошибок или отсутствия ключевых функциональных возможностей возрастает.

  3. Развитие замедляется, и тестирование на самом деле не улучшается, поскольку фасады уже проверяются. Быстрое развитие – это сильная сторона использования laravel в первую очередь. Время всегда является ограничением.

  4. Большинство других проектов используют ларавельные фасады. Большинство людей, имеющих опыт использования laravel, используют фасады. Создание проекта, который не соответствует существующим тенденциям предыдущих проектов, замедляет работу в целом. Будущие неопытные (или ленивые!) Разработчики могут игнорировать вставку фасада, и проект может оказаться смешанным. (Даже рецензенты кода являются людьми)