Использование инъекции зависимостей над фасадами Laravel
Я читал ряд источников, которые намекают на то, что фасад laravel в конечном счете существует для удобства, и что эти классы должны вместо этого вводиться для обеспечения слабой связи. Даже у Тейлора Отвелла есть пост, объясняющий, как это сделать. Кажется, я не единственный, кто задается этим вопросом.
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");
}
}
Это нормально, за исключением того, что я начинаю обнаруживать, что некоторые конструкторы и методы начинают принимать параметры four+.
Так как IoC Laravel, кажется, вводит только в конструкторы классов и определенные методы (контроллеры), даже когда у меня довольно простые функции и классы, я обнаружил, что конструкторы классов упаковываются с необходимыми классами, которые затем вводятся в необходимые методы.
Теперь я обнаружил, что если я продолжу в том же духе, мне понадобится мой собственный контейнер IoC, что похоже на переизобретение колеса, если я использую фреймворк типа laravel?
Например, я использую сервисы для управления логикой бизнеса/представления, а не контроллеров, которые с ними работают - они просто маршрутизируют представления. Таким образом, контроллер сначала получает соответствующую service
, а затем parameter
в своем URL. Одна сервисная функция также должна проверять значения из формы, поэтому мне нужны Request
и 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 может устранить мою проблему?
Тем не менее, по-видимому, нет окончательного консенсуса по этому вопросу...
Ответы
Ответ 1
Это одно из преимуществ внедрения в конструктор - это становится очевидным, когда ваш класс слишком много делает, потому что параметры конструктора становятся слишком большими.
Первое, что нужно сделать, это разделить контроллеры, у которых слишком много обязанностей.
Скажем, у вас есть контроллер страницы:
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
и AboutController
.
После того, как вы это сделали, если у вас все еще слишком много * зависимостей, пришло время искать то, что я назову косвенными зависимостями (потому что я не могу придумать правильное имя для них!) - зависимости, которые непосредственно не используются зависимым классом, но вместо этого перешел на другую зависимость.
Примером этого является 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 зависимостями, а не безпараметрический и множество статических вызовов, скрытых во всех методах
Ответ 2
Одна проблема с фасадами заключается в том, что дополнительный код должен быть написан для поддержки их при автоматическом модульном тестировании.
Что касается решений:
1. Разрешение зависимостей вручную
Один из способов разрешения зависимостей, если вы не хотите это делать. конструкторы или методы инъекции, - это напрямую вызвать приложение():
/* @var $email_services App\Contracts\EmailServicesContract
$email_services = app('App\Contracts\EmailServicesContract');
2. Рефакторинг
Иногда, когда я нахожу, что передаю слишком много сервисов или зависимостей в класс, возможно, я нарушил принцип единой ответственности. В таких случаях может потребоваться перепроектировать, разбив службу или зависимость на более мелкие классы. Я бы использовал другую услугу, чтобы объединить связанную группу классов, чтобы служить чему-то как фасад. По сути, это будет иерархия классов услуг/логики.
Пример. У меня есть служба, которая генерирует рекомендуемые продукты и отправляет их пользователям по электронной почте. Я вызываю службу WeeklyRecommendationServices
, и она принимает 2 других службы в качестве зависимости - a Recommendation
услуги, которые являются черным ящиком для генерации рекомендаций (и у нее есть свои зависимости - возможно, репо для продуктов, помощник или два) и EmailService
, которые могут иметь Mailchimp в качестве зависимости). Некоторые низкоуровневые зависимости, такие как перенаправления, валидаторы и т.д., Будут в этих дочерних службах вместо службы, которая действует как точка входа.
3. Использовать глобальные функции Laravel
Некоторые из Фасадов доступны как вызовы функций в Laravel 5. Например, вы можете использовать redirect()->back()
вместо Redirect::back()
, а также view('some_blade)
вместо View::make('some_blade')
. Я считаю, что то же самое для dispatch
и некоторых других часто используемых фасадов.
(Отредактировано для добавления) 4. Использование признаков
Поскольку я работал над поставленными задачами сегодня, я также замечаю, что другой способ встраивания зависимостей заключается в использовании признаков. Например, признак DispathaysJobs в Laravel имеет следующие строки:
protected function dispatch($job)
{
return app('Illuminate\Contracts\Bus\Dispatcher')->dispatch($job);
}
Любой класс, который использует черты, будет иметь доступ к защищенному методу и получить доступ к зависимости. Это более аккуратно, чем наличие многих зависимостей в сигнатурах конструктора или метода, более ясное (о каких зависимостях задействовано), чем глобальные и более легкие для настройки, чем ручные вызовы контейнеров DI. Недостатком является то, что каждый раз, когда вы вызываете функцию, которую вы должны получить из зависимости от контейнера DI,
Ответ 3
Методы класса, которые составляют часть механизма маршрутизации в Laravel (промежуточное программное обеспечение, контроллеры и т.д.) также имеют свои типы-подсказки, используемые для ввода зависимостей - они не все должны быть введены в конструктор. Это может помочь сохранить ваш конструктор тонким, хотя я не знаком ни с одним из четырех правил ограничения параметров; PSR-2 позволяет растягивать определение метода на несколько строк, предположительно потому, что нередко требуется более четырех параметров.
В вашем примере вы можете внедрить службы Request
и Validator
в конструкторе в качестве компромисса, поскольку они часто используются несколькими способами.
Что касается установления консенсуса - Laravel должен быть более самоуверенным, поскольку приложения должны быть достаточно похожими, чтобы использовать подход, основанный на одном уровне. Более простой вызов - это то, что я думаю, что фасады будут идти по пути додо в будущей версии.
Ответ 4
Хорошо, ваши мысли и заботы, и правильно, и я тоже их.
Есть некоторые преимущества 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";
}
}
Просто помните, чтобы разбить свой код на небольшие отдельные части, каждый из которых несет ответственность.
Когда вы правильно нарушаете свой код, в большинстве случаев у вас не будет так много параметров конструктора, и код будет легко тестироваться и высмеиваться.
Просто последнее примечание: если вы создаете небольшое приложение или даже страницу в огромном приложении, например, "контактную страницу" и "контактную страницу отправки", вы можете, конечно, сделать все в контроллере с фасадами, просто зависит от сложности проекта.
Ответ 5
Я люблю laravel из-за его прекрасной архитектуры. Теперь, как с моего подхода, я бы не вводил все фасады в контроллер только потому, что? Injecting Redirect фасады только в неправильной практике контроллера, поскольку это может потребоваться в другом. И главным образом, вещи, которые в основном используются, должны быть объявлены для всех, а для тех, кто использует некоторые или только тогда их наилучшую практику, чтобы вводить их с помощью метода, так как когда вы заявляете, что это будет затруднять оптимизацию вашей памяти, а также скорость вашего код. Надеюсь, это поможет
Ответ 6
Не столько ответ, сколько пища для размышлений после разговора с моими коллегами, которые сделали несколько очень важных моментов;
-
Если внутренняя структура laravel изменяется между версиями (что произошло в прошлом, очевидно,), впрыскивание разрешенных путей класса фасадов разбило бы все на обновление - в основном используя стандартные фасады и вспомогательные методы (если не полностью) избегает этой проблемы.
-
Хотя развязка кода, как правило, хорошая вещь, накладные расходы на внедрение этих разрешенных путей класса фасадов заставляют классы загромождать. Для разработчиков, принимающих участие в проекте, больше времени тратится на выполнение кода, который можно было бы лучше потратить на исправление ошибок или тестирование. Новые разработчики должны помнить, какие введенные классы являются разработчиками и которые являются larvels. Разработчики, незнакомые с laravel под капотом, должны тратить время на поиск API. В конечном счете вероятность появления ошибок или отсутствующих ключевых функциональных возможностей возрастает.
-
Развитие замедляется, а тестируемость не улучшается, поскольку фасады уже проверяются. Быстрое развитие - это сильная сторона использования laravel в первую очередь. Время всегда является ограничением.
-
В большинстве других проектов используются ларавельные фасады. Большинство людей, имеющих опыт использования laravel, используют фасады. Создание проекта, который не соответствует существующим тенденциям предыдущих проектов, замедляет работу в целом. Будущие неопытные (или ленивые!) Разработчики могут игнорировать вставку фасада, и проект может оказаться смешанным. (Даже рецензенты кода являются людьми)