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
ogsession.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.
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.
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
.
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.
Å 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');
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');
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 ();
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.
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.
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.