Graver inn til Laravel's IoC Container

Inversion of Control, eller IoC, er en teknikk som gjør at kontrollen kan inverteres i forhold til klassisk prosesskode. Den mest fremtredende formen av IoC er selvsagt Dependency Injection, eller DI. Laravel's IoC-beholder er en av de mest brukte Laravel-funksjonene, men er nok den minste forstått.

Her er et veldig raskt eksempel på bruk av Dependency Injection for å oppnå Inversion of Control.

drivstoff = $ drivstoff;  offentlig funksjon refuel ($ liter) return $ liter * $ this-> fuel-> getPrice ();  klasse bensin offentlig funksjon getPrice () return 130.7;  $ bensin = ny bensin; $ bil = ny JeepWrangler ($ bensin); $ cost = $ car-> refuel (60);

Ved å bruke konstruktørinjeksjon har vi nå delegert opprettelsen av vår Bensin forekomme tilbake til den som ringer seg selv, og dermed oppnå inversjon av kontroll. Våre JeepWrangler trenger ikke å vite hvor Bensin kommer fra, så lenge det blir det.

Så hva har alt dette å gjøre med Laravel? Ganske mye, faktisk. Laravel, hvis du ikke visste, er egentlig en IOC-beholder. En beholder er et objekt som, som du kan forvente, inneholder tingene. Laravel's IoC-beholder brukes til å inneholde mange, mange forskjellige bindinger. Alt du gjør i Laravel vil til enhver tid ha en interaksjon med IoC-beholderen. Denne interaksjonen er generelt i form av en binding som blir løst.

Hvis du åpner noen av de eksisterende Laravel-tjenesteleverandørene, ser du mest sannsynlig noe i denne registrere metode (eksempel har blitt forenklet, mye).

$ this-> app ['router'] = $ denne-> app-> del (funksjon ($ app) returner ny ruter;);

Det er en veldig, veldig grunnleggende binding. Den består av navnet på bindingen (router) og en resolver (lukkingen). Når bindingen er løst fra beholderen, får vi en forekomst av Router returnert.

Laravel grupperer vanligvis lignende bindende navn, for eksempel økt og session.store.

For å løse bindingen kan vi bare ringe en metode direkte av den, eller bruke gjøre metode på beholderen.

$ router = $ this-> app-> make ('router');

Det er hva beholderen gjør i sin mest grunnleggende form. Men, som de fleste ting Laravel, er det mye mer til det enn bare bindende og løse klasser.

Delt og ikke-delt bindinger

Hvis du har sett gjennom flere av Laravel-tjenesteleverandørene, vil du legge merke til at de fleste bindinger er definert som det tidligere eksemplet. Her er det igjen:

$ this-> app ['router'] = $ denne-> app-> del (funksjon ($ app) returner ny ruter;);

Denne bindingen bruker dele metode på beholderen. Laravel bruker en statisk variabel til å lagre en tidligere oppløst verdi og vil bare gjenopprette den verdien når en binding er løst igjen. Dette er i utgangspunktet hva dele metoden gjør det.

$ this-> app ['router'] = funksjon ($ app) static $ router; hvis (is_null ($ router)) $ router = ny router;  returner $ router; ;

En annen måte å skrive på dette ville være å bruke bindShared metode.

$ this-> app-> bindShare ('router', funksjon ($ app) return new Router;);

Du kan også bruke singleton og forekomst metoder for å oppnå en felles binding. Så, hvis de alle oppnår det samme, hva er forskjellen? Ikke mye, faktisk. Jeg personlig foretrekker å bruke bindShared metode.

Betinget Binding

Det kan være tider når du vil binde noe til beholderen, men bare når det ikke allerede er bundet før. Det er noen måter du kan gå om dette, men det enkleste er å bruke bindIf metode.

$ this-> app-> bindIf ('router', funksjon ($ app) return new ImprovedRouter;);

Dette vil bare binde til containeren hvis router bindende eksisterer ikke allerede. Det eneste du må være oppmerksom på her er hvordan du deler en betinget binding. For å gjøre dette må du levere en tredje parameter til bindIf metode med en verdi på ekte.

Automatisk avhengighetsoppløsning

En av de mest brukte funksjonene i IoC-beholderen er dens evne til automatisk å løse avhengighet for klasser som er ikke bundet. Hva betyr dette, akkurat? For det første trenger vi faktisk ikke å binde noe til beholderen for å løse en forekomst. Vi kan bare gjøre en forekomst av omtrent hvilken som helst klasse.

klasse bensin offentlig funksjon getPrice () return 130.7;  // I vår tjenesteleverandør ... $ bensin = $ this-> app-> make ('Bensin');

Beholderen vil instantiere vår Bensin klasse for oss. Den beste delen av dette er at det også vil løse byggeproblemer for oss.

klasse JeepWrangler offentlig funksjon __construct (bensin $ brensel) $ this-> fuel = $ fuel;  offentlig funksjon refuel ($ liter) return $ liter * $ this-> fuel-> getPrice ();  // I vår tjenesteleverandør ... $ car = $ this-> app-> make ('JeepWrangler');

Det første som beholderen gjør er å inspisere avhengighetene til JeepWrangler klasse. Det vil da forsøke å løse disse avhengighetene. Så fordi vår JeepWrangler type-hint på Bensin klassen, vil beholderen automatisk løse og injisere den som en avhengighet.

Beholderen kan ikke automatisk injisere ikke-typehintede avhengigheter. Så hvis en av dine avhengigheter er en matrise, må du ordne det manuelt eller gi parameteren en standardverdi.

Bindende implementeringer

Å ha Laravel automatisk løse dine avhengigheter er flott og forenkler prosessen med å instansere klasser manuelt. Det er imidlertid tider når du vil at en bestemt implementering skal injiseres, spesielt når du bruker grensesnitt. Dette oppnås enkelt ved å bruke det fullt kvalifiserte navnet på klassen som bindende. For å demonstrere dette bruker vi et nytt grensesnitt som heter Brensel.

grensesnitt Brensel offentlig funksjon getPrice (); 

Nå vår JeepWrangler klassen kan skrive hint grensesnittet, og vi vil sørge for at vår Bensin klassen implementerer grensesnittet.

klasse JeepWrangler offentlig funksjon __construct (Brensel $ brensel) $ this-> fuel = $ fuel;  offentlig funksjon refuel ($ liter) return $ liter * $ this-> fuel-> getPrice ();  klasse Bensinimplementer Drivstoff offentlig funksjon getPrice () return 130.7; 

Vi kan nå binde vår Brensel grensesnitt til beholderen og få det til å løse en ny forekomst av Bensin.

$ this-> app-> bind ('Drivstoff', 'Bensin'); // Eller vi kunne instansere det selv. $ this-> app-> bind ('Brensel', funksjon ($ app) returner ny bensin;);

Nå når vi lager en ny forekomst av vår JeepWrangler, Beholderen vil se at den ber om det Brensel, og det vil vite å injisere en forekomst av Bensin.

Dette gjør det også veldig enkelt å bytte ut en implementering, da vi bare kan endre bindingen i beholderen. For å demonstrere, kan vi begynne å fylle bilen med premium bensin, noe som er litt dyrere.

klasse PremiumPetrol implementerer Drivstoff offentlig funksjon getPrice () return 144.3;  // I vår tjenesteleverandør ... $ this-> app-> bind ('Brensel', 'PremiumPetrol');

Kontekstuelle bindinger

Legg merke til at kontekstuelle bindinger bare er tilgjengelige i Laravel 5.

En kontekstuell binding gjør at du kan binde en implementering (som vi gjorde ovenfor) til en bestemt klasse.

abstrakt klasse Bil offentlig funksjon __construct (Brensel $ brensel) $ this-> fuel = $ fuel;  offentlig funksjon refuel ($ liter) return $ liter * $ this-> fuel-> getPrice (); 

Vi lager da en ny NissanPatrol klasse som utvider abstrakt klassen, og vi oppdaterer vår JeepWrangler å forlenge det også.

klasse JeepWrangler utvider bil // klasse NissanPatrol utvider bil //

Til slutt skal vi opprette en ny diesel klasse som implementerer Brensel grensesnitt.

klasse Diesel implementerer Drivstoff offentlig funksjon getPrice () return 135.3; 

Nå vil vår Jeep Wrangler fylle på bensin, og vår Nissan Patrol vil påfylles med diesel. Hvis vi prøvde å bruke samme metode som tidligere ved å binde en implementering til grensesnittet, ville begge disse bilene få samme type drivstoff, noe som ikke er det vi vil ha.

For å sikre at hver bil fylles med riktig drivstoff, kan vi informere beholderen hvilken implementering som skal brukes i hver kontekst.

$ Dette-> app-> når ( 'JeepWrangler') -> behov ( 'Fuel') -> gi ( 'Bensin'); $ Dette-> app-> når ( 'NissanPatrol') -> behov ( 'Fuel') -> gi ( 'Diesel');

tagging

Merk at merking bare er tilgjengelig i Laravel 5.

Å være i stand til å løse bindinger fra beholderen er ganske viktig. Normalt kan vi bare løse noe hvis vi vet hvordan det har vært bundet til beholderen. Med Laravel 5 kan vi nå merke våre bindinger slik at utviklere enkelt kan løse alle bindinger som har samme tag.

Hvis du utvikler et program som tillater andre utviklere å lage plugins, og du vil kunne enkelt løse alle disse pluginene, vil koder være svært nyttige.

$ this-> app-> tag ('awesome.plugin', 'plugin'); // Eller en rekke koder. $ tags = ['plugin', 'theme']; $ this-> app-> tag ('awesome.plugin', $ tags);

Nå, for å løse alle bindingene for en gitt kode kan vi bruke tagget metode.

$ plugins = $ this-> app-> tagged ('plugin'); foreach ($ plugins som $ plugin) $ plugin-> doSomethingFunky (); 

Rebounds og Rebinding

Når du binder noe til beholderen med samme navn mer enn en gang, kalles det gjenoppretting. Laravel vil legge merke til at du knytter noe igjen og vil utløse en tilbakegang.

Den største fordelen her er når du utvikler en pakke som tillater andre utviklere å utvide den ved å gjenopprette komponenter i beholderen. For å bruke den må vi implementere setter injeksjon på vår Bil abstrakt.

abstrakt klasse Bil offentlig funksjon __construct (Brensel $ brensel) $ this-> fuel = $ fuel;  offentlig funksjon refuel ($ liter) return $ liter * $ this-> fuel-> getPrice ();  offentlig funksjon setFuel (drivstoff $ brensel) $ this-> fuel = $ fuel; 

La oss anta at vi er bindende vår JeepWrangler til beholderen som så.

$ this-> app-> bindShare ('fuel', funksjon ($ app) returner ny bensin;); $ this-> app-> bindShare ('bil', funksjon ($ app) returner ny JeepWrangler ($ app ['fuel']););

Dette er helt greit, men la oss si at en annen utvikler kommer med og ønsker å utvide dette og bruke premium bensin i bilen. Så de bruker setFuel Metode for å injisere sitt nye drivstoff inn i bilen.

$ this-> app ['car'] -> setFuel (ny PremiumPetrol);

I de fleste tilfeller kan dette være alt som trengs; Men hva om pakken vår blir mer komplisert og brensel bindende injiseres i flere andre klasser? Dette ville føre til at den andre utvikleren måtte sette sin nye forekomst en hel rekke ganger. Så, for å løse dette, kan vi gjøre bruk av gjenoppretting:

$ this-> app-> bindShare ('bil', funksjon ($ app) returner ny JeepWrangler ($ app-> rebinding ('fuel', funksjon ($ app, $ fuel) $ app ['bil'] - > setFuel ($ fuel);)););

De rebinding Metoden vil straks returnere til oss den allerede bundet forekomsten, slik at vi kan bruke den i konstruktøren til vår JeepWrangler. Lukkingen gitt til rebinding Metoden mottar to parametere, den første er IoC-beholderen og den andre er den nye bindingen. Vi kan da bruke setFuel Metode oss selv å injisere den nye bindingen i vår JeepWrangler forekomst.

Alt som er igjen er for en annen utvikler å bare rebind brensel i beholderen. Deres tjenesteleverandør kan se slik ut:

$ this-> app-> bindShare ('fuel', function () returner ny PremiumPetrol;);

Når bindingen er tilbake i beholderen, vil Laravel automatisk utløse de tilhørende lukkene. I vårt tilfelle den nye PremiumPetrol eksempel vil bli satt på vår JeepWrangler forekomst.

utvide

Hvis du vil injisere en avhengighet i en av kjernebindingene eller en binding som er opprettet av en pakke, så vil forlenge Metoden på beholderen er en av de enkleste måtene å gå om. 

Denne metoden vil løse bindingen fra beholderen og utføre et lukning med beholderen og det løst forekomsten som parametere. Dette gjør at du enkelt kan løse og injisere dine egne bindinger, eller bare ordne en ny klasse og injisere den.

$ this-> app-> extend ('bil', funksjon ($ app, $ bil) $ car-> setFuel (ny PremiumPetrol););

I motsetning til gjenoppretting vil dette bare angi avhengigheten av en enkelt binding.

Bruk utenfor Laravel

Som mange av de belysende komponentene som utgjør Laravel-rammen, kan containeren brukes utenfor Laravel i en frittstående applikasjon. For å gjøre det må du først kreve det som en avhengighet i din composer.json fil.

"krever": "belyse / container": "4.2. *"

Dette vil installere det siste 4.2 versjon av beholderen. Nå er alt som er igjen å gjøre, instantiere en ny container.

krever 'leverandør / autoload.php'; $ app = new Illuminate \ Container \ Container; $ app-> bind Del ('bil', funksjon () returner ny JeepWrangler;);

Av alle komponentene er dette et av de enkleste å bruke, uansett hvor du trenger en fleksibel og utstyrt IoC-beholder.