Utilisation de l'injection de dépendance sur les façades laravel

J'ai lu un certain nombre de sources qui suggèrent que laravel facade existe finalement pour plus de commodité et que ces classes devraient plutôt être injectées pour permettre un couplage lâche. Même Taylor Otwell a un post expliquant comment faire. Il semble que je ne suis pas le seul à me le demander .

use Redirect;

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

Deviendrait

use IlluminateRoutingRedirector as Redirect;

class Example class
{
    protected $redirect;

    public function __constructor(Redirect $redirect)
    {
        $this->redirect = $redirect
    }

    public function example()
    {
         return $this->redirect->route("route.name");
    }
}

C'est bien sauf que je commence à constater que certains constructeurs et méthodes commencent à prendre quatre+ paramètres.

Puisque le Laravel IoC semble injecter uniquement dans les constructeurs de classes et certaines méthodes (contrôleurs), même quand j'ai des fonctions et des classes assez maigres, je constate que les constructeurs des classes sont emballés avec les classes nécessaires qui sont ensuite injectées dans les méthodes nécessaires.

Maintenant, je constate que si je continue cette approche, j'aurai besoin de mon propre conteneur IoC, ce qui ressemble à réinventer la roue si j'utilise un framework comme laravel?

Par exemple, j'utilise des services pour contrôler la logique métier / vue plutôt que des contrôleurs qui les traitent - ils routent simplement les vues. Ainsi, un contrôleur prendra d'abord son service correspondant, puis le parameter dans son url. Une fonction de service doit également vérifier les valeurs d'un formulaire, alors j'ai besoin de Request et Validator. Juste comme ça, j'ai quatre paramètres.

// MyServiceInterface is binded using the laravel container
use InterfacesMyServiceInterface;
use IlluminateHttpRequest;
use IlluminateValidationFactory 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]);
    }
}

Ceci est un seul exemple. En réalité, beaucoup de mes constructeurs ont déjà plusieurs classes être injecté (modèles, Services, paramètres, façades). J'ai commencé à' décharger ' l'injection du constructeur (le cas échéant) à l'injection de méthode, et les classes appelant ces méthodes utilisent leurs constructeurs pour injecter des dépendances à la place.

On m'a dit que plus de quatre paramètres pour un constructeur de méthode ou de classe en règle générale sont une mauvaise pratique / odeur de code. Cependant je ne vois pas comment vous pouvez vraiment éviter cela si vous choisissez le chemin de l'injection laravel façade.

Ai-je mal compris cette idée? Mes classes / fonctions ne sont-elles pas assez maigres? Est-ce que je manque le point de conteneur laravels ou Ai-je vraiment besoin de penser à créer mon propre conteneur IoC? Certains autres réponses semble faire allusion à la laravel récipient étant en mesure d'éliminer mon problème?

Cela dit, il ne semble pas y avoir de consensus définitif sur la question...

40
demandé sur Community 2016-01-26 13:10:38

6 réponses

C'est L'un des avantages de l'injection constructeur - cela devient évident lorsque votre classe fait beaucoup, car les paramètres du constructeur deviennent trop grands.

1ère chose à faire est de diviser les contrôleurs qui ont trop de responsabilités.

Dites que vous avez un contrôleur de page:

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
    }
}

C'est un candidat principal pour la division en deux contrôleurs, ClientController et AboutController.

Une fois que vous avez fait cela, si vous avez encore trop de dépendances*, il est temps de chercher ce que je appellera des dépendances indirectes (parce que je ne peux pas penser au nom propre pour eux!)- dépendances qui ne sont pas directement utilisées par la classe dépendante, mais transmises à une autre dépendance.

Un exemple de ceci est addClientAction - Il nécessite une requête et un validateur, juste pour les passer au clientRepostory.

Nous pouvons re factoriser en créant une nouvelle classe spécifiquement pour créer des clients à partir de requêtes, réduisant ainsi nos dépendances, et simplifiant à la fois le contrôleur et le référentiel:

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

    public function getClient(){}
    public function isValid(){}
    public function getErrors(){}
}

Notre méthode devient maintenant:

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

Il N'y a pas de règle stricte quant au nombre de dépendances qui sont trop nombreuses. Les bonnes nouvelles sont que si vous avez construit votre application en utilisant loose-couplage, re-factoring est relativement simple.

Je préférerais beaucoup voir un constructeur avec 6 ou 7 dépendances qu'un constructeur sans paramètre et un tas d'appels statiques cachés dans les méthodes

18
répondu Steve 2018-04-12 10:19:08

Un problème avec les façades est que du code supplémentaire doit être écrit pour les prendre en charge lors des tests unitaires automatisés.

Quant aux solutions:

1. Résolution manuelle des dépendances

Une façon de résoudre les dépendances, si vous ne souhaitez pas le faire via. constructeurs ou méthodes d'injection, est d'appeler app () directement:

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

2. Refactoring

Parfois, quand je me retrouve à passer trop de services, ou de dépendances dans un classe, peut-être que J'ai violé la Responsabilité Unique Principe. Dans ces cas, peut-être qu'une nouvelle conception est nécessaire, en divisant le service ou la dépendance en classes plus petites. J'utiliserais un autre service pour envelopper un groupe de classes connexes pour servir quelque chose comme une façade. En substance, ce sera une hiérarchie de services / classes logiques.

Exemple: j'ai un service qui génère des produits recommandés et l'envoie aux utilisateurs par e-mail. J'appelle le service WeeklyRecommendationServices, et il prend 2 autres services comme dépendance - un Recommendation services qui est une boîte noire pour générer les recommandations (et il a ses propres dépendances - peut-être un repo pour les produits, un assistant ou deux), et un EmailService qui a peut-être Mailchimp comme dépendance). Certaines dépendances de niveau inférieur, telles que les redirections, les validateurs, etc. sera dans ces services à l'enfance au lieu du service qui agit comme le point d'entrée.

3. Utiliser les fonctions globales Laravel

Certaines façades sont disponibles en fonction appelle Laravel 5. Par exemple, vous pouvez utiliser redirect()->back() au lieu de Redirect::back(), comme view('some_blade) au lieu de View::make('some_blade'). Je crois que c'est la même chose pour dispatch et d'autres façades couramment utilisées.

(modifié pour ajouter) 4. Utilisation des traits Comme je travaillais sur des tâches en file d'attente aujourd'hui, j'observe également qu'une autre façon d'injecter des dépendances est d'utiliser des traits. Par exemple, le trait DispathcesJobs dans Laravel a les lignes suivantes:

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

Toute classe qui utilise les traits aura accès à la méthode protégée et accès à la dépendance. Il est plus propre que d'avoir de nombreuses dépendances dans les signatures du constructeur ou de la méthode, est plus clair (sur les dépendances impliquées) que les globales et plus facile à personnaliser que les appels de conteneur DI manuels. L'inconvénient est que chaque fois que vous appelez la fonction, vous devez récupérer la dépendance du conteneur DI,

2
répondu Extrakun 2016-02-05 15:44:49

Méthodes de classe qui font partie du mécanisme de routage dans Laravel (middleware, contrôleurs, etc.) ont également leurs indices de type utilisés pour injecter des dépendances - ils n'ont pas tous besoin d'être injectés dans le constructeur. Cela peut aider à garder votre constructeur mince, même si Je ne suis familier avec aucune règle de limite de quatre paramètres; PSR-2 permet à la définition de la méthode d'être étirée sur plusieurs lignes probablement parce qu'il n'est pas rare d'exiger plus de quatre paramètre.

Dans votre exemple, vous pouvez injecter les services Request et Validator dans le constructeur en tant que compromis, car ils sont souvent utilisés par Plus d'une méthode.

Quant à l'établissement d'un consensus - Laravel devrait être plus opiniâtre pour que les applications soient suffisamment similaires pour utiliser une approche unique. Un appel plus facile cependant est que je pense que les façades iront dans le sens du dodo dans une future version.

1
répondu tjbp 2016-01-27 00:45:03

Eh bien vos pensées et préoccupations et corriger et je les avais aussi bien. Il y a quelques avantages des façades ( Je ne les utilise généralement pas ), mais si vous utilisez juste, je suggère de les utiliser uniquement dans les contrôleurs, car les contrôleurs ne sont que des points d'entrée et de sortie pour moi au moins.

Pour l'exemple que vous avez donné, je vais montrer comment je le gère généralement:

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

N'oubliez pas de casser votre code en petits morceaux individuels que chacun gère sa propre responsabilité. Lorsque vous correctement cassez votre code, dans la plupart des cas, vous n'aurez pas autant de paramètres de constructeur, et le code sera facilement testable et moqué.

Juste une dernière note, si vous construisez une petite application ou même une page dans une énorme application par exemple une "page de contact" et "page de contact submit" , vous pouvez sûrement tout faire dans le contrôleur avec des façades, cela dépend simplement de la complexité du projet.

1
répondu Tzook Bar Noy 2016-02-03 07:35:01

J'adore le laravel en raison de sa belle architecture.Maintenant, à partir de mon approche, je n'injecterais pas toutes les façades dans la méthode du contrôleur seulement pourquoi? Injecter des façades de redirection uniquement dans les mauvaises pratiques du contrôleur comme il pourrait en avoir besoin dans d'autres. Et principalement les choses qui sont principalement utilisées devraient être déclarées pour tous tandis que pour ceux qui utilisent une partie ou seulement alors sa meilleure pratique pour les injecter via la méthode car lorsque vous déclarez en haut cela entravera l'optimisation de votre mémoire ainsi que la vitesse de votre code. J'espère que cela aiderait

1
répondu ujwal dhakal 2016-02-04 10:26:19

Pas tellement une réponse mais un peu de matière à réflexion après avoir parlé à mes collègues qui ont fait quelques points très valables;

  1. Si la structure interne de laravel est modifiée entre les versions (ce qui est arrivé dans le passé apparemment), l'injection des chemins de classe de façade résolus casserait tout sur une mise à niveau - tout en utilisant les façades par défaut et les méthodes d'assistance principalement (sinon complètement) évite ce problème.

  2. Bien que le code de découplage soit généralement un bonne chose, la surcharge de l'injection de ces chemins de classe de façade résolus rend les classes encombrées - pour les développeurs prenant en charge le projet, Plus de temps est consacré à essayer de suivre le code qui pourrait être mieux dépensé pour corriger les bugs ou tester. Les nouveaux développeurs doivent se rappeler quelles classes injectées sont des développeurs et quelles sont les laravels. Les développeurs qui ne connaissent pas laravel sous le capot doivent passer du temps à chercher L'API. En fin de compte la probabilité d'introduire des bugs ou des fonctionnalités clés manquantes augmenter.

  3. Le développement est ralenti et la testabilité n'est pas vraiment améliorée puisque les façades sont déjà testables. Le développement rapide est un point fort de l'utilisation de laravel en premier lieu. Le temps est toujours une contrainte.

  4. La Plupart des autres projets, laravel façades. La plupart des personnes ayant de l'expérience en utilisant laravel utilisent des façades. Créer un projet qui ne suit pas les tendances existantes des projets précédents ralentit les choses en général. Futur inexpérimenté (ou paresseux!) les développeurs peuvent ignorer l'injection de façade et le projet peut se retrouver avec un format mixte. (Même les réviseurs de code sont humains)

0
répondu myol 2016-01-29 10:23:33