In Symfony2, perché è una ctriggers idea iniettare il contenitore del servizio, piuttosto che i singoli servizi?

Non riesco a trovare la risposta a questo …

Se inserisco il contenitore del servizio, ad esempio:

// config.yml my_listener: class: MyListener arguments: [@service_container] my_service: class: MyService // MyListener.php class MyListener { protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function myFunction() { $my_service = $this->container->get('my_service'); $my_service->doSomething(); } } 

allora funziona altrettanto bene che se lo faccio:

 // config.yml my_listener: class: MyListener arguments: [@my_service] my_service: class: MyService // MyListener.php class MyListener { protected $my_service; public function __construct(MyService $my_service) { $this->my_service = $my_service; } public function myFunction() { $this->my_service->doSomething(); } } 

Quindi, perché non dovrei semplicemente iniettare il contenitore del servizio e ottenere i servizi da quello all’interno della mia class?

Il mio elenco dei motivi per i quali dovresti preferire l’iniezione di servizi:

  1. La tua class dipende solo dai servizi di cui ha bisogno, non dal contenitore del servizio. Ciò significa che il servizio può essere utilizzato in un ambiente che non utilizza il contenitore del servizio Symfony. Ad esempio, puoi trasformare il tuo servizio in una libreria che può essere usata in Laravel, Phalcon, ecc. – la tua class non ha idea di come vengano iniettate le dipendenze.

  2. Definendo le dipendenze a livello di configurazione, è ansible utilizzare lo scaricatore di configurazione per sapere quali servizi utilizzano quali altri servizi. Ad esempio, iniettando @mailer , è abbastanza facile lavorare dal contenitore dei servizi in cui è stato iniettato il mailer. D’altra parte, se fai $container->get('mailer') , allora l’unico modo per scoprire dove viene utilizzato il mailer è di fare una find .

  3. Riceverai una notifica sulle dipendenze mancanti al momento della compilazione del contenitore, anziché in fase di runtime. Ad esempio, immagina di aver definito un servizio, che stai iniettando in un listener. Alcuni mesi dopo, si elimina accidentalmente la configurazione del servizio. Se si sta iniettando il servizio, verrai avvisato non appena si svuota la cache. Se si inserisce il contenitore del servizio, verrà rilevato l’errore solo quando il listener ha esito negativo a causa del fatto che il contenitore non può ottenere il servizio. Certo, potresti farlo se hai un approfondito test di integrazione, ma … hai avuto dei test di integrazione approfonditi, vero? 😉

  4. Lo saprai prima se ti stai iniettando il servizio sbagliato. Ad esempio, se hai:

     public function __construct(MyService $my_service) { $this->my_service = $my_service; } 

    Ma hai definito l’ascoltatore come:

     my_listener: class: Whatever arguments: [@my_other_service] 

    Quando il listener riceve MyOtherService , PHP genera un errore, che ti dice che sta ricevendo la class sbagliata. Se stai facendo $container->get('my_service') stai dando per scontato che il container stia restituendo la class giusta, e può volerci molto tempo per capire che non è il suo.

  5. Se si sta utilizzando un IDE, digitare hint aggiunge un intero carico di ulteriore aiuto. Se stai usando $service = $container->get('service'); allora il tuo IDE non ha idea di cosa sia il $service . Se si inietta con

     public function __construct(MyService $my_service) { $this->my_service = $my_service; } 

    allora il tuo IDE sa che $this->my_service è un’istanza di MyService e può offrire aiuto con nomi di metodi, parametri, documentazione, ecc.

  6. Il tuo codice è più facile da leggere. Tutte le tue dipendenze sono definite proprio in cima alla class. Se sono sparsi per tutta la class con $container->get('service') allora può essere molto più difficile da capire.

  7. Il tuo codice è più facile da testare. Se stai iniettando il container del servizio, devi prendere in giro il contenitore del servizio e configurare il mock per restituire i mock dei servizi pertinenti. Iniettando direttamente i servizi, basta prendere in giro i servizi e iniettarli – si salta un intero livello di complicazioni.

  8. Non lasciatevi ingannare dall’errore “consente il caricamento lazy”. È ansible configurare il caricamento lazy a livello di configurazione , semplicemente contrassegnando il servizio come lazy: true .

Personalmente, l’unica volta che ho iniettato il contenitore del servizio è stata la migliore soluzione ansible quando stavo cercando di iniettare il contesto di sicurezza in un listener di dottrine. Ciò stava generando un’eccezione di riferimento circolare, poiché gli utenti erano memorizzati nel database. Il risultato è stato che la doctrine e il contesto di sicurezza erano dipendenti l’uno dall’altro in fase di compilazione. Inserendo il contenitore del servizio, sono riuscito a superare la dipendenza circolare. Tuttavia, questo può essere un odore di codice, e ci sono modi per aggirarlo (ad esempio, usando il dispatcher di eventi ), ma ammetto che la complicazione aggiunta può superare i benefici.

Non è una buona idea perché stai facendo dipendere la tua class dalla DI. Cosa succede quando un giorno decidi di tirare fuori la tua class e usarla su un progetto completamente diverso? Ora non sto parlando di Symfony o di PHP, sto parlando in generale. In tal caso, devi assicurarti che il nuovo progetto utilizzi lo stesso tipo di meccanismo DI con gli stessi metodi supportati o ottieni eccezioni. E cosa succede se il progetto non utilizza affatto la DI o utilizza una nuova implementazione DI nuova? Devi passare attraverso l’intera base di codice e cambiare le cose per supportare il nuovo DI. Nei progetti di grandi dimensioni, questo può essere problematico e costoso, soprattutto quando si trascina più di una sola class.

È meglio rendere le tue lezioni il più indipendenti ansible. Questo significa mantenere il DI fuori dal tuo solito codice, qualcosa come una terza persona che decide cosa va dove, indica dove vanno le cose, ma non va lì e lo fa da solo. Questo è il modo in cui lo capisco.

Sebbene, come ha detto tomazahlin, sono d’accordo che nei progetti Symfony in rare occasioni aiuta a prevenire le dipendenze circolari. Questo è l’unico esempio in cui lo userei e mi farei dannatamente sicuro che sia l’unica opzione.

Oltre a tutti gli svantaggi spiegati da altri (nessun controllo sui servizi usati, la compilazione del tempo di esecuzione, le dipendenze mancanti, ecc.)

C’è una ragione principale, che rompe il vantaggio principale dell’utilizzo di DIC – Sostituzione delle dipendenze .

Se il servizio è definito in libreria, non sarai in grado di sostituirlo con le dipendenze locali per soddisfare le tue esigenze.

Solo questa ragione è abbastanza forte da non iniettare DIC intere. Hai appena rotto l’idea di sostituire le dipendenze poiché sono HARDCODED! in servizio;)

BTW. Non dimenticare di richiedere le interfaces nel costruttore di servizi anziché di classi specifiche il più ansible, sempre una buona sostituzione delle dipendenze.

EDIT: esempio di sostituzione delle dipendenze

Definizione del servizio in alcuni fornitori:

    

Sostituzione nella tua app:

    

Ciò consente di sostituire la logica del fornitore con quella personalizzata, ma non dimenticare di implementare l’interfaccia di class richiesta. Con le dipendenze hardcoded non è ansible sostituire la dipendenza in un’unica posizione.

Puoi anche sostituire il servizio vendor_dependency , ma questo lo sostituirà in tutti i posti non solo in vendor_service .

Iniettare l’intero contenitore non è una buona idea in generale. Bene, funziona, ma perché iniettare l’intero contenitore, mentre hai solo bisogno di pochi altri servizi o parametri.

A volte si desidera iniettare l’intero contenitore per evitare riferimenti circolari, perché se si inietta l’intero contenitore, si ottiene un “caricamento lento” dei servizi richiesti. Un esempio potrebbe essere la doctrine dell’ quadro ascoltatori.

È ansible ottenere il contenitore da ogni class che è “Container Aware” o ha accesso al kernel.