Funksjonell programmering i PHP

Den nye hype i programmering handler om funksjonelle programmeringsparadigmer. Funksjonelle språk brukes mer og mer i større og bedre applikasjoner. Scala, Haskel, etc. er blomstrende og andre, mer konservative språk som Java begynte å vedta noen av de funksjonelle programmeringsparadigmaene (se nedleggelser i Java7 og lat eval for lister i Java8). Men det eneste få folk vet er at PHP er ganske allsidig når det gjelder funksjonell programmering. Alle de viktigste funksjonelle programmeringskonseptene kan uttrykkes i PHP. Så, hvis du er ny til funksjonell programmering, vær forberedt på å få tankene dine blåst, og hvis du allerede er kjent med funksjonell programmering, vær forberedt på å ha det gøy med denne opplæringen.


Programmeringsparadismer

Uten programmeringsparadigmer kunne vi gjøre hva vi vil, uansett hva vi vil. Selv om dette vil føre til ekstrem fleksibilitet, vil det også føre til umulige arkitekturer og veldig oppblåst kode. Dermed ble programmeringsparadigmer oppfunnet for å hjelpe oss, programmører, å tenke på en bestemt måte om et bestemt program, og på den måten begrense vår evne til å uttrykke vår løsning.

Hvert programmeringsparadigm tar bort en frihet fra oss:

  • Modulær programmering tar bort ubegrenset programstørrelse.
  • Strukturert og prosedyreprogrammering tar bort "go-to" og begrenser programmerer til sekvens, valg og iterasjon.
  • Objektorientert programmering tar bort poengene til funksjoner.
  • Funksjonell programmering tar bort oppdrag og mutable tilstand.

Funksjonsprogrammeringsprinsipper

I funksjonell programmering har du ingen data representert av variabler.

I funksjonell programmering alt er en funksjon. Og jeg mener alt. For eksempel kan et sett, som i matematikk, bli representert som flere funksjoner. En matrise eller liste er også en funksjon eller en gruppe funksjoner.

I objektorientert programmering er alt et objekt. Og et objekt er en samling av data og metoder som gjør handlinger på disse dataene. Objekter har en tilstand, en flyktig, mutable tilstand.

I funksjonell programmering har du ingen data representert av variabler. Det finnes ingen databeholdere. Data er ikke tilordnet en variabel. Noen verdier kan defineres og tilordnes. Men i de fleste tilfeller er de funksjoner som er tilordnet "variabler". Jeg har satt "variabler" mellom anførselstegn fordi de i funksjonell programmering er uforanderlige. Selv om de fleste funksjonelle programmeringsspråk ikke håndhever uforanderlighet, på samme måte som de fleste objektorienterte språk ikke håndhever objekter, hvis du endrer verdien etter et oppdrag, gjør du ikke rent funksjonell programmering lenger.

Fordi du ikke har verdier som er tilordnet variabler, har du i funksjonell programmering ingen stat.

På grunn av ingen tilstand og ingen oppgaver, i funksjonell programmering funksjoner har ingen bivirkning. Og på grunn av de tre tidligere grunnene, funksjoner er alltid forutsigbare. Dette betyr i utgangspunktet at hvis du ringer en funksjon med samme parametre igjen og igjen og igjen ... vil du alltid ha det samme resultatet. Dette er en stor fordel i forhold til objektorientert programmering og reduserer kompleksiteten av multi-threaded og massivt multi-threaded applikasjoner.

Men hvis vi ønsker å uttrykke alt i funksjoner, må vi kunne tildele dem parametre eller returnere dem fra andre funksjoner. Dermed krever funksjonell programmering støtten til høyere rekkefølge funksjoner. Dette betyr i utgangspunktet at en funksjon kan tilordnes en "variabel", sendt inn som en parameter til en annen funksjon, og returneres som følge av en funksjon.

Til slutt, fordi vi ikke har noen verdier i variabler, er og for looper uvanlige for funksjonell programmering og erstattet med rekursjon.


Vis meg koden!

Nok snakk og filosofi for en leksjon. La oss kode!

Sett opp et PHP-prosjekt i din favoritt IDE eller kode redigerer. Lag i det a "tester" mappe. Lag to filer: FunSets.php i prosjektets mappe, og FunSetsTest.php i testmappen. Vi vil lage et søknad, med tester, som vil representere begrepet sett.

I matematikk er et sett en samling av forskjellige objekter, betraktet som et objekt i seg selv. (Wikipedia)

Det betyr i utgangspunktet at sett er en haug med ting på ett sted. Disse settene kan være og er preget av matematiske operasjoner: fagforeninger, veikryss, forskjeller, etc. Og ved brukbare egenskaper som: inneholder.

Våre programmeringsbegrensninger

Så la oss kode! Men vent. Hvordan? Vel, for å respektere konseptene for funksjonell programmering må vi bruke følgende restriksjoner på vår kode:

  • Ingen oppgaver. - Vi har ikke lov til å tilordne verdier til variabler. Vi har imidlertid lov til å tilordne funksjoner til variabler.
  • Ingen muterbar tilstand. - Vi har ikke lov til å endre verdien av oppgaven når det gjelder et oppdrag. Vi har heller ikke lov til å endre verdien av en variabel som hadde sin verdi satt som parameter for gjeldende funksjon. Så, ingen endring av parametere.
  • Ingen stund og for løkker. - Vi har ikke lov til å bruke PHPs "mens" og "for" kommandoer. Vi kan imidlertid definere vår egen metode for å sykle gjennom elementene i et sett og kalle det foreach / for / while.

Ingen begrensninger gjelder for tester. På grunn av arten av PHPUnit, bruker vi klassisk objektorientert PHP-kode der. For å bedre imøtekomme våre tester vil vi også pakke inn hele vår produksjonskode i en enkelt klasse.

Settets Definer Funksjon

Hvis du er en erfaren programmerer, men ukjent med funksjonell programmering, Nå er det på tide å slutte å tenke som du vanligvis gjør og vær klar til å forlate din komfortsone. Glem alle dine tidligere måter å redegjøre for et problem og forestill deg alt i funksjoner.

Den definerende funksjonen til et sett er dens "inneholder" metode.

funksjonen inneholder ($ set, $ elem) return $ set ($ elem); 

OK ... Dette er ikke så opplagt, så la oss se hvordan vi skal bruke den.

$ set = funksjon ($ element) return true;; inneholder ($ sett, 100);

Vel, dette forklarer det litt bedre. Funksjonen "Inneholder" har to parametre:

  • $ sett - representerer et sett definert som en funksjon.
  • $ elem - representerer et element definert som en verdi.

I denne sammenheng, alt det "Inneholder" må gjøre er å bruke funksjonen i $ sett med parameteren $ elem. La oss pakke inn alt i en test.

klassen FunSetsTest utvider PHPUnit_Framework_TestCase private $ funSets; beskyttet funksjon setUp () $ this-> funSets = new FunSets ();  funksjonstestContainsIsImplemented () // Vi karakteriserer et sett av dets inneholder funksjon. Det er den grunnleggende funksjonen til et sett. $ set = funksjon ($ element) return true;; $ this-> assertTrue ($ this-> funSets-> inneholder ($ set, 100)); 

Og pakk vår produksjonskode innvendig FunSets.php inn i en klasse:

klassen FunSets offentlig funksjon inneholder ($ set, $ elem) return $ set ($ elem); 

Du kan faktisk kjøre denne testen, og den vil passere. Settet vi definerte for denne testen er bare en funksjon som alltid returnerer sant. Det er et "sant sett".

Singleton-settet

Hvis det forrige kapittelet var litt forvirrende eller så ubrukelig i logikk, vil dette forklare det litt. Vi ønsker å definere et sett med et enkelt element, et singleton sett. Husk at dette må være en funksjon, og vi vil ønske å bruke den som i testen nedenfor.

funksjon testSingletonSetContainsSingleElement () // En singleton sett er karakteriserer av en funksjon som passert til inneholder vil returnere sant for enkeltelementet // passert som parameter. Med andre ord er en singleton et sett med et enkelt element. $ singleton = $ this-> funSets-> singletonSet (1); $ this-> assertTrue ($ this-> funSets-> inneholder ($ singleton, 1)); 

Vi må definere en funksjon som kalles "SingeltonSet" med en parameter som representerer et element av settet. I testen er det nummer ett (1). Da forventer vi vår inneholder metode, når den kalles med en singleton-funksjon, for å returnere ekte hvis den sendte parameteren er lik en. Koden som gir testen, er som følger:

offentlig funksjon singletonSet ($ elem) return function ($ otherElem) bruk ($ elem) return $ elem == $ otherElem; ; 

Wow! Det er vilt. Så, funksjonen "SingletonSet" blir som et parameter et element som $ elem. Deretter returnerer den en annen funksjon som har en parameter $ otherElem og denne andre funksjonen vil sammenligne $ elem til $ otherElem.

Så, hvordan virker dette? Først, denne linjen:

$ singleton = $ this-> funSets-> singletonSet (1);

er forvandlet til hva "SingletonSet (1)" avkastning:

$ singleton = funksjon ($ otherElem) return 1 == $ otherElem; ;

Deretter "inneholder ($ singleton, 1)" er kalt. Som igjen kalles det som er i $ singleton. Så blir koden:

$ Singleton (1)

Som faktisk utfører koden i den med $ otherElem å ha verdien en.

returnere 1 == 1

Det er selvfølgelig sant og vår test passerer.

Leter du allerede? Føler du at hjernen din begynner å koke? Jeg gjorde sikkert da jeg først skrev dette eksemplet i Scala, og jeg gjorde det igjen da jeg først skrev dette eksemplet i PHP. Jeg tror dette er ekstraordinært. Vi klarte å definere et sett med ett element, med muligheten til å kontrollere at den inneholder verdien vi passerte inn i den. Vi gjorde alle disse uten en enkelt verdioppgave. Vi har ingen variabel som inneholder verdien en eller har en tilstand av en. Ingen tilstand, ingen oppgave, ingen mutability, ingen løkker. Vi er på rett spor her.


Union of Sets

Nå som vi kan lage et sett med en enkelt verdi, må vi kunne lage et sett med flere verdier. Den åpenbare måten å gjøre det på er å definere unionsoperasjonen på settene våre. Sammenslutningen av to singleton-sett vil representere en annen union med begge verdier. Jeg vil at du tar et øyeblikk og tenker på løsningen før du ruller til koden, kanskje ta en topp på testene nedenfor.

funksjon testUnionContainsAllElements () // En union er preget av en funksjon som får 2 sett som parametere og inneholder alle de angitte settene // Vi kan bare lage singletoner på dette punktet, så vi lager 2 singletoner og forener dem $ s1 = $ dette -> funSets-> singletonSet (1); $ s2 = $ this-> funSets-> singletonSet (2); $ union = $ this-> funSets-> union ($ s1, $ s2); // Nå, kontroller at både 1 og 2 er en del av foreningen $ this-> assertTrue ($ this-> funSets-> inneholder ($ union, 1)); $ this-> assertTrue ($ this-> funSets-> inneholder ($ union, 2)); // ... og at den ikke inneholder 3 $ this-> assertFalse ($ this-> funSets-> inneholder ($ union, 3)); 

Vi vil ha en funksjon som heter "Union" Det får to parametere, begge sett. Husk at sett er bare funksjoner for oss, så vår "Union" funksjonen får to funksjoner som parametere. Da ønsker vi å kunne sjekke med "Inneholder" hvis foreningen inneholder et element eller ikke. Så, vår "Union" funksjonen må returnere en annen funksjon som "Inneholder" kan bruke.

($ s1, $ s2) return function $ this-> inneholder ($ s2, $ otherElem); ; 

Dette fungerer faktisk ganske bra. Og det er helt gyldig, selv når unionen din blir kalt med en annen union og en singleton. Det ringer inneholder inne i seg selv for hver parameter. Hvis det er en union, vil den rekruttere. Det er så enkelt.


Intersect og forskjell

Vi kan bruke samme linerlogikk med mindre endringer for å få de to neste viktigste funksjonene som karakteriserer et sett: skjæringspunktet - inneholder bare de vanlige elementene mellom to sett - og forskjellen - inneholder bare de elementene fra det første settet som ikke er delte av det andre settet.

($ s1, $ s2) return function ($ otherElem) bruk ($ s1, $ s2) return $ this-> inneholder ($ s1, $ otherElem) && $ this-> inneholder ($ s2, $ otherElem); ;  offentlig funksjon diff ($ s1, $ s2) return funksjon ($ otherElem) bruk ($ s1, $ s2) return $ this-> inneholder ($ s1, $ otherElem) &&! $ this-> inneholder ($ s2 , $ otherElem); ; 

Jeg vil ikke oversvømme deg med testkoden for disse to metodene. Testene er skrevet og du kan sjekke dem hvis du ser i vedlagte kode.


Filter Set

Vel, dette er litt mer komplisert, vi vil ikke kunne løse dette med en enkelt linje med kode. Et filter er en funksjon som bruker to parametere: et sett og en filtreringsfunksjon. Det bruker filtreringsfunksjonen til et sett og returnerer et annet sett som bare inneholder elementene som tilfredsstiller filtreringsfunksjonen. For bedre å forstå det, er det testen for det.

funksjon testFilterKonfigurerOnlyElementsThatMatchConditionFunction () $ u12 = $ this-> createUnionWithElements (1, 2); $ u123 = $ this-> funSets-> union ($ u12, $ this-> funSets-> singletonSet (3)); // Filtreringsregel, finn elementer som er større enn 1 (betyr 2 og 3) $ betingelse = funksjon ($ elem) return $ elem> 1;; // Filtrert sett $ filteredSet = $ this-> funSets-> filter ($ u123, $ betingelse); // Bekreft filtrert sett inneholder ikke 1 $ this-> assertFalse ($ this-> funSets-> inneholder ($ filteredSet, 1), "Bør ikke inneholde 1"); // Sjekk det inneholder 2 og 3 $ dette-> assertTrue ($ this-> funSets-> inneholder ($ filteredSet, 2), "Bør inneholde 2"); $ this-> assertTrue ($ this-> funSets-> inneholder ($ filteredSet, 3), "Bør inneholde 3");  privat funksjon createUnionWithElements ($ elem1, $ elem2) $ s1 = $ this-> funSets-> singletonSet ($ elem1); $ s2 = $ this-> funSets-> singletonSet ($ elem2); returnere $ this-> funSets-> union ($ s1, $ s2); 

Vi lager et sett med tre elementer: 1, 2, 3. Og vi legger det inn i variabelen $ u123 så det er lett å identifisere i våre tester. Deretter definerer vi en funksjon som vi vil søke på testen og plassere den i $ tilstand. Til slutt, vi kaller filter på vår $ u123 sett med $ tilstand og sett det resulterende settet inn i $ filteredSet. Så kjører vi påstander med "Inneholder" for å avgjøre om settet ser ut som vi ønsker. Vår tilstandsfunksjon er enkel, den vil returnere sann hvis elementet er større enn ett. Så vårt siste sett bør bare inneholde verdiene to og tre, og dette er hva vi sjekker inn i våre påstander.

($ set, $ betingelse) return ($ set, $ betingelse) return function ($ otherElem) bruk ($ set, $ betingelse) if ($ condition ($ otherElem)) returner $ this-> inneholder ($ set, $ otherElem); returner falsk; ; 

Og her går du. Vi implementerte filtrering med bare tre linjer med kode. Mer presist, hvis betingelsen gjelder det angitte elementet, kjører vi et innhold på settet for det elementet. Hvis ikke, returnerer vi nettopp falsk. Det er det.


Looping Over Elements

Det neste trinnet er å skape ulike looping funksjoner. Den første, "for alle()", vil ta en $ sett og a $ tilstand og returnere ekte hvis $ tilstand gjelder for alle elementene i $ sett. Dette fører til følgende test:

funksjon testForAllCorrectlyTellsIfAllElementsSatisfyCondition () $ u123 = $ this-> createUnionWith123 (); $ higherThanZero = funksjon ($ elem) return $ elem> 0; ; $ higherThanOne = funksjon ($ elem) return $ elem> 1; ; $ higherThanTwo = funksjon ($ elem) return $ elem> 2; ; $ this-> assertTrue ($ this-> funSets-> forall ($ u123, $ higherThanZero)); $ this-> assertFalse ($ this-> funSets-> forall ($ u123, $ higherThanOne)); $ this-> assertFalse ($ this-> funSets-> forall ($ u123, $ higherThanTwo)); 

Vi hentet ut $ u123 Opprettelse fra den forrige testen til en privat metode. Deretter definerer vi tre forskjellige forhold: høyere enn null, høyere enn en og høyere enn to. Siden vårt sett inneholder tallene ett, to og tre, bare den høyere enn null tilstanden skal returnere sann, resten skal være feil. Faktisk kan vi gjøre testpasset ved hjelp av en annen rekursiv metode som brukes til å iterere over alle elementene.

privat $ bound = 1000; privat funksjon forallIterator ($ currentValue, $ set, $ betingelse) hvis ($ currentValue> $ this-> bound) return true; elseif ($ this-> inneholder ($ set, $ currentValue)) returnere $ betingelse ($ currentValue) && $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); ellers returnere $ this-> forallIterator ($ currentValue + 1, $ set, $ condition);  offentlig funksjon forall ($ set, $ betingelse) return $ this-> forallIterator (- $ this-> bound, $ set, $ condition); 

Vi starter med å definere noen grenser for vårt sett. Verdiene må være mellom -1000 og +1000. Dette er en rimelig begrensning vi pålegger for å holde dette eksemplet enkelt nok. Funksjonen "for alle" vil ringe til den private metoden "ForallIterator" med de nødvendige parametrene for å bestemme om alle elementer respekterer tilstanden. I denne funksjonen tester vi først om vi er ute av grensene. Hvis ja, returner sant. Sjekk deretter om vårt sett inneholder gjeldende verdi og returner nåverdien som er brukt på tilstanden, sammen med en logisk "OG" med et rekursivt anrop til oss selv med den neste verdien. Ellers kan du bare ringe oss med den neste verdien og returnere resultatet.

Dette fungerer fint, vi kan implementere det på samme måte som "Eksisterer ()". Dette returnerer ekte hvis noen av elementene tilfredsstiller tilstanden.

privat funksjon existsIterator ($ currentValue, $ set, $ betingelse) hvis ($ currentValue> $ this-> bound) return false; elseif ($ this-> inneholder ($ set, $ currentValue)) returnere $ betingelse ($ currentValue) || $ this-> existsIterator ($ currentValue + 1, $ set, $ condition); ellers returnere $ this-> existsIterator ($ currentValue + 1, $ set, $ condition);  offentlig funksjon eksisterer ($ set, $ betingelse) return $ this-> existsIterator (- $ this-> bound, $ set, $ condition); 

Den eneste forskjellen er at vi kommer tilbake falsk når du er ute av grensene, og vi bruker "ELLER" i stedet for "OG" i den andre hvis.

Nå, "kart()" vil være annerledes, enklere og kortere.

($ setElem) $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $  returner $ currentElem == $ action ($ elem);); ; 

Kartlegging betyr at vi bruker en handling til alle elementene i et sett. For kart trenger vi ingen hjelper iterator, vi kan gjenbruke "Eksisterer ()" og returnere de elementene av "eksisterer" som tilfredsstiller resultatet av $ handling påføres $ element. Dette kan ikke være åpenbart på første side, så la oss se hva som skjer.

  • Vi sender settet 1, 2 og handlingen $ element * 2 (dobbel) å kartlegge.
  • Det vil returnere en funksjon, åpenbart, som har en parameter som et element og bruker settet og handlingen fra ett nivå høyere.
  • Denne funksjonen vil ringe finnes med settet 1, 2 og tilstandsfunksjonen $ currentElement er lik $ elem * 2.
  • finnes () vil iterere over alle elementene mellom -1000 og +1000, våre grenser. Når den finner et element, dobbel av hva som kommer fra "Inneholder" (verdien av $ currentElement) det kommer tilbake ekte.
  • Med andre ord, vil den siste sammenligningen komme tilbake ekte for anropet inneholder med verdi to, når verdien av gjeldende multiplisert med to, resulterer i to. Så for det første elementet i settet, en, vil det komme tilbake på to. For det andre elementet, to, på verdi fire.

Et praktisk eksempel

Vel, funksjonell programmering er morsom, men det er langt fra ideell i PHP. Så jeg anbefaler ikke deg å skrive hele applikasjoner på denne måten. Men nå som du har lært hva PHP kan gjøre funksjonelt, kan du bruke deler av denne kunnskapen i dine daglige prosjekter. Her er et eksempel på godkjenningsmodul. De AuthPlugin klassen gir en metode som mottar en bruker og et passord og gjør det magisk for å autentisere brukeren og angi sine tillatelser.

klasse AuthPlugin private $ permissions = array (); funksjon autentiser ($ brukernavn, $ passord) $ this-> verifiser bruker ($ brukernavn, $ passord); $ adminModules = nye AdminModules (); $ this-> tillatelser [] = $ adminModules-> allowRead ($ brukernavn); $ this-> tillatelser [] = $ adminModules-> allowWrite ($ brukernavn); $ this-> tillatelser [] = $ adminModules-> allowExecute ($ brukernavn);  privat funksjon verifiserer brukeren ($ brukernavn, $ passord) // ... BRUKER / PASS KONTROLL // ... LOAD BRUKER DETALJER, ETC. 

Nå kan det høres bra ut, men det har et stort problem. 80% av "Autentisere ()" Metode bruker informasjon fra "AdminModules". Dette skaper en svært sterk avhengighet.


Det ville være mye mer rimelig å ta de tre samtalene og lage en enkelt metode på AdminModules.


Så, ved å flytte generasjonen inn i AdminModules vi klarte å redusere tre avhengigheter til bare en. Det offentlige grensesnittet til AdminModules ble også redusert fra tre til bare en enkelt metode. Vi er imidlertid ikke der ennå. AuthPlugin fortsatt avhengig av AdminModules.

En objektorientert tilnærming

Hvis vi vil at vår autentiseringsplugg skal kunne brukes av en hvilken som helst modul, må vi definere et felles grensesnitt for disse modulene. La oss injisere avhengigheten og introdusere et grensesnitt.

klasse AuthPlugin private $ permissions = array (); privat $ appModule; funksjon __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  funksjonen autentiserer ($ brukernavn, $ passord) $ this-> verifiser bruker ($ brukernavn, $ passord); $ this-> permissions = array_merge ($ this-> tillatelser, $ this-> appModule-> getPermissions ($ brukernavn));  privat funksjon verifiserer brukeren ($ brukernavn, $ passord) // ... BRUKER / PASS KONTROLL // ... LOAD BRUKER DETALJER, ETC. 

AuthPlugin fikk en konstruktør. Det blir en parameter av typen ApplicationModule, et grensesnitt og samtaler "getPermissions ()" på dette injiserte objektet.

grensesnitt ApplicationModule offentlig funksjon getPermissions ($ brukernavn); 

ApplicationModule definerer en enkelt offentlig metode, "getPermissions ()", med et brukernavn som en parameter.

klasse AdminModules implementerer ApplicationModule // [...]

Endelig, AdminModules må gjennomføre ApplicationModule grensesnitt.


Nå er dette mye bedre. Våre AuthPlugin Avhenger kun av et grensesnitt. AdminModules Avhenger av det samme grensesnittet, så AuthPlugin ble modul agnostisk. Vi kan lage et hvilket som helst antall moduler, alle implementeringer ApplicationModule, og AuthPlugin vil kunne jobbe med alle dem.

En funksjonell tilnærming

En annen måte å reversere avhengigheten av og gjøre AdminModule, eller annen modul, for å bruke AuthPlugin er å injisere i disse modulene en avhengighet av AuthPlugin. AuthPlugin vil gi en måte å angi godkjenningsfunksjonen på, og hver applikasjon vil sende i sin egen "GetPermission ()" funksjon.

klasse AdminModules private $ authPlugin; funksjon __construct (Authentitcation $ authPlugin) $ this-> authPlugin = $ authPlugin;  privat funksjon allowRead ($ brukernavn) return "ja";  privat funksjon allowWrite ($ brukernavn) return "no";  privat funksjon allowExecute ($ brukernavn) return $ username == "joe"? "Ja Nei";  privat funksjon autentiser () $ this-> authPlugin-> setPermissions (funksjon ($ brukernavn) $ permissions = array (); $ tillatelser [] = $ this-> allowRead ($ brukernavn); $ tillatelser [] = $ dette-> allowWrite ($ brukernavn); $ tillatelser [] = $ this-> allowExecute ($ brukernavn); returner $ tillatelser;); $ Dette-> authPlugin-> godkjenne (); 

Vi starter med AdminModule. Det implementerer ikke noe lenger. Det bruker imidlertid et injisert objekt som må implementere godkjenning. I AdminModule det blir en "Autentisere ()" metode som vil ringe "setPermissions ()"AuthPlugin og pass i funksjonen som må brukes.

grensesnittautentisering funksjon setPermissions ($ permissionGrantingFunction); funksjonen autentiser (); 

Autentiseringsgrensesnittet definerer bare de to metodene.

klassen AuthPlugin implementerer godkjenning private $ permissions = array (); privat $ appModule; private $ permissionsfunksjon; funksjon __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  funksjonen autentiserer ($ brukernavn, $ passord) $ this-> verifiser bruker ($ brukernavn, $ passord); $ this-> permissions = $ this-> permissionsFunction ($ brukernavn);  privat funksjon verifiserer brukeren ($ brukernavn, $ passord) // ... BRUKER / PASS KONTROLL // ... LOAD BRUKER DETALJER, ETC.  offentlig funksjon setPermissions ($ permissionGrantingFunction) $ this-> permissionsFunction = $ permissionGrantingFunction; 

Endelig, AuthPlugin implementerer Autentisering og setter innkommende funksjon i et privat klasseattributt. Deretter, "godkjenning()" blir en dum metode. Det ringer bare funksjonen og setter deretter returverdien. Det er helt avkoblet av det som kommer inn.


Hvis vi ser på skjemaet, er det to viktige endringer:

  • I stedet for AdminModule, AuthPlugin er den som implementerer grensesnittet.
  • AuthPlugin vil "ringe tilbake" AdminModule, eller hvilken som helst annen modul sendt i tillatelsesfunksjonen.

Hvilken å bruke?

Det er ikke noe riktig svar på dette spørsmålet. Jeg ville hevde at hvis prosessen med å bestemme tillatelsene er ganske avhengig av applikasjonsmodulen, så er objektorientert tilnærming mer hensiktsmessig. Men hvis du tror at hver applikasjonsmodul skal kunne gi en autentiseringsfunksjon, og din AuthPlugin er bare et skjelett som gir autentiseringsfunksjonaliteten, men uten å vite noe om brukere og prosedyrer, så kan du gå med funksjonell tilnærming.

Den funksjonelle tilnærmingen gjør din AuthPlugin veldig abstrakt og du kan stole på det. Men hvis du planlegger å tillate din AuthPlugin å gjøre mer og vite mer om brukere og systemet, da blir det for konkret, og du vil ikke stole på det. I så fall velg objektorienterte måter og la betongen AuthPlugin avhenger av de mer abstrakte applikasjonsmodulene.