Использование инъекций зависимости через фасады 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");
    }
}

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

Поскольку 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 может устранить мою проблему?

Тем не менее, по-видимому, нет окончательного консенсуса по этому вопросу...

6 ответов

Решение

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

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

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

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 зависимостями, а не безпараметрический и множество статических вызовов, скрытых во всех методах

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

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

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

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

/* @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. Использование черт Поскольку я сегодня работал над заданиями в очереди, я также заметил, что еще один способ внедрить зависимости - это использовать черты. Например, черта DispathcesJobs в Laravel имеет следующие строки:

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

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

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

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

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

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

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

Для примера, который вы привели, я покажу, как я в целом справляюсь:

// 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";
    }
}

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

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

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

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

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

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

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

Другие вопросы по тегам