Подтвердить что ты не робот

Использование инъекции зависимостей над фасадами 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 может устранить мою проблему?

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

4b9b3361

Ответ 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, используют фасады. Создание проекта, который не соответствует существующим тенденциям предыдущих проектов, замедляет работу в целом. Будущие неопытные (или ленивые!) Разработчики могут игнорировать вставку фасада, и проект может оказаться смешанным. (Даже рецензенты кода являются людьми)