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


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

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, что похоже на изобретение колеса, если я использую такую структуру, как ларавель?

Например, я использую сервисы для управления логикой бизнеса/представления, а не контроллеры, работающие с ними - они просто маршрутизируют представления. Таким образом, контроллер сначала примет соответствующий ему 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]);
    }
}

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

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

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

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

Author: Community, 2016-01-26

6 answers

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

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

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

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

 18
Author: Steve, 2018-04-12 10:19:08

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

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

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,

 2
Author: Extrakun, 2016-02-05 15:44:49

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

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

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

 1
Author: tjbp, 2016-01-27 00:45:03

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

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

// 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
Author: Tzook Bar Noy, 2016-02-03 07:35:01

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

 1
Author: ujwal dhakal, 2016-02-04 10:26:19

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

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

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

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

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

 0
Author: myol, 2016-01-29 10:23:33