web-dev-qa-db-fra.com

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

J'ai lu un certain nombre de sources qui suggèrent que la façade en laravel existe finalement pour des raisons 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 cela. Il semble que je ne suis pas le seul à me demande cela .

use Redirect;

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

deviendrait 

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

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

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

Maintenant, je constate que si je continue dans cette approche, j'aurai besoin de mon propre conteneur IoC, ce qui donne l’impression de réinventer la roue si j’utilise un cadre tel que laravel?

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

// 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]);
    }
}

Ceci est un exemple unique. En réalité, plusieurs de mes constructeurs ont déjà injecté plusieurs classes (Modèles, Services, Paramètres, Façades). J'ai commencé à "décharger" l'injection de constructeur (le cas échéant) en 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 sont généralement des mauvaises pratiques/odeurs de code. Cependant, je ne vois pas comment vous pouvez vraiment éviter cela si vous choisissez la voie de l’injection de façades en laravel.

Est-ce que j'ai une mauvaise idée? Mes cours/fonctions ne sont-ils pas assez maigres? Est-ce que je manque le but du conteneur laravels ou dois-je vraiment penser à créer mon propre conteneur IoC? Certains autres réponses semblent faire allusion au fait que le conteneur de laravel puisse éliminer mon problème? 

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

41
myol

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

La première chose à faire est de séparer les contrôleurs qui ont trop de responsabilités.

Disons 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 idéal pour la scission 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 rechercher ce que j'appellerai des dépendances indirectes (car je ne peux pas penser à leur nom!) - dépendances qui ne sont pas directement utilisées par la classe dépendante , mais au lieu de cela transmis à une autre dépendance.

Un exemple de ceci est addClientAction - il nécessite une demande et un validateur, juste pour les transmettre à la clientRepostory.

Nous pouvons re factoriser en créant une nouvelle classe spécialement 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 absolue quant au nombre de dépendances trop nombreuses. La bonne nouvelle est que si vous avez construit votre application en utilisant un couplage faible, la re-factorisation est relativement simple. 

Je préférerais de beaucoup voir un constructeur avec 6 ou 7 dépendances plutôt qu'un constructeur sans paramètre et un tas d'appels statiques cachés tout au long des méthodes

21
Steve

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

En ce qui concerne les solutions:

1. Résolution des dépendances manuellement

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

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

2. Refactoring

Parfois, lorsque je passe trop de services ou de dépendances dans une classe, j'ai peut-être enfreint le principe de responsabilité unique. Dans ces cas, une refonte est peut-être nécessaire, en divisant le service ou la dépendance en classes plus petites. J'utiliserais un autre service pour terminer un groupe de classes apparentées afin de servir quelque chose de 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 les envoie aux utilisateurs par courrier électronique. J'appelle le service WeeklyRecommendationServices, et il prend en compte 2 autres services en tant que dépendance - un service Recommendation qui est une boîte noire pour générer les recommandations (et il a ses propres dépendances - peut-être une prise en pension pour les produits, une aide ou deux), et une EmailService ayant peut-être Mailchimp comme dépendance). Certaines dépendances de niveau inférieur, telles que les redirections, les validateurs, etc., se trouveront dans ces services enfants au lieu du service servant de point d'entrée.

3. Utiliser les fonctions globales Laravel

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

(Modifié pour ajouter) 4. Utilisation de traits .__ Alors que 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 à la dépendance. Cela vaut mieux que d'avoir plusieurs dépendances dans les signatures du constructeur ou de la méthode, que cela est plus clair (à propos des dépendances impliquées) que globales et plus facile à personnaliser que les appels manuels de conteneurs DI. L’inconvénient est que chaque fois que vous appelez la fonction, vous devez récupérer la dépendance à partir du conteneur DI,

2
Extrakun

J'aime 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, mais pourquoi? Injecter des façades de redirection uniquement dans les mauvaises pratiques du contrôleur, comme cela pourrait être le cas dans d'autres. Et surtout, les choses les plus utilisées doivent être déclarées pour tous, alors que pour ceux qui en utilisent, la meilleure pratique consiste à les injecter via une méthode, car, lorsque vous déclarez au sommet, cela entrave l'optimisation de votre mémoire ainsi que la vitesse de votre. code. J'espère que cela aiderait

1
ujwal dhakal

Les méthodes de classe qui font partie du mécanisme de routage dans Laravel (middleware, contrôleurs, etc.) utilisent également leurs indicateurs de type pour injecter des dépendances ; constructeur. Cela peut aider à garder votre constructeur mince, même si je ne connais aucune règle de base à quatre paramètres; PSR-2 permet d'étendre la définition de la méthode sur plusieurs lignes , probablement parce qu'il n'est pas rare d'exiger plus de quatre paramètres.

Dans votre exemple, vous pouvez injecter les services Request et Validator dans le constructeur, car ils sont souvent utilisés par plusieurs méthodes.

En ce qui concerne l’établissement d’un consensus - Laravel devrait être plus avisé pour que les applications soient suffisamment similaires pour pouvoir 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 version future.

1
tjbp

Bien, vos pensées et vos préoccupations sont correctes et je les ai aussi .. .. Il y a quelques avantages de Facades (je ne les utilise généralement pas), mais si vous les utilisez, je vous suggérerais de les utiliser uniquement dans les contrôleurs, comme 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";
    }
}

Rappelez-vous simplement de casser votre code en petites parties individuelles, chacune assumant sa propre responsabilité . Lorsque vous casser correctement votre code, dans la plupart des cas, vous n'aurez pas autant de paramètres de constructeur et le code sera facilement testable et simulé.

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

1
Tzook Bar Noy

Ce n’est pas vraiment une réponse, mais quelques pistes de réflexion après avoir parlé à mes collègues qui ont soulevé des points très valables;

  1. Si la structure interne de laravel est modifiée entre les versions (ce qui est apparemment arrivé dans le passé), l'injection des chemins de classe de façade résolus briserait tout lors d'une mise à niveau - l'utilisation des méthodes de façade et des méthodes d'assistance par défaut évite ce problème de manière majeure . 

  2. Bien que le découplage du code soit généralement une bonne chose, la surcharge liée à l’injection de ces chemins de classe de façade résolus encombrent les classes. Les nouveaux développeurs doivent se souvenir des classes injectées qui sont des développeurs et des laravels. Les développeurs peu familiarisés avec laravel sous le capot doivent passer du temps à rechercher l'API. En fin de compte, la probabilité d'introduire des bogues ou des fonctionnalités manquantes augmente.

  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 utilisent des façades en laravel. La plupart des personnes expérimentées dans l'utilisation des façades en laravel. La création d'un projet qui ne suit pas les tendances existantes des projets précédents ralentit les choses en général. Les futurs développeurs inexpérimentés (ou paresseux!) Pourraient ignorer l'injection de façade et le projet pourrait se retrouver avec un format mixte. (Même les réviseurs de code sont humains)

0
myol