Programare funcțională în PHP

Noul hype în programare se referă la paradigme de programare funcțională. Limbile funcționale sunt utilizate din ce în ce mai mult în aplicații mai mari și mai bune. Scala, Haskel etc. sunt înfloritoare și alte limbi mai conservatoare, cum ar fi Java, au început să adopte unele paradigme de programare funcțională (a se vedea închiderea în Java7 și evalul leneș pentru listele din Java8). Cu toate acestea, ceea ce puțini oameni știu este că PHP este destul de versatil când vine vorba de programarea funcțională. Toate conceptele principale de programare funcțională pot fi exprimate în PHP. Deci, dacă sunteți nou în programarea funcțională, fiți pregătiți să vă răstălmăciți mintea și dacă sunteți deja familiarizați cu programarea funcțională, fiți pregătiți să vă distrați cu acest tutorial.


Paradigme de programare

Fără paradigme de programare am putea să facem tot ce vrem în orice mod dorim. În timp ce acest lucru ar duce la o flexibilitate extremă, ar duce, de asemenea, la arhitecturi imposibile și un cod foarte umflat. Astfel, paradigmele de programare au fost inventate pentru a ne ajuta, programatorii, să gândim într-un mod specific despre un anumit program și, în acest fel, să ne limităm capacitatea de a ne exprima soluția.

Fiecare paradigmă de programare îndepărtează o libertate de la noi:

  • Modulare de programare ia mărimea programului nelimitat.
  • Programarea structurată și procedurală iau "go-to" și limitează programatorul la secvență, selecție și repetare.
  • Programarea orientată pe obiecte îndepărtează indicii la funcții.
  • Programarea funcțională îndepărtează sarcina și starea mutabilă.

Principiile de programare funcțională

În programarea funcțională nu aveți date reprezentate de variabile.

În programarea funcțională totul este o funcție. Și vreau să spun totul. De exemplu, un set, ca și în matematică, poate fi reprezentat ca mai multe funcții. Un tabel sau o listă este, de asemenea, o funcție sau un grup de funcții.

În programarea orientată obiect totul este un obiect. Un obiect este o colecție de date și metode care fac acțiuni asupra acelor date. Obiectele au o stare, o stare volatilă, mutabilă.

În programarea funcțională nu aveți date reprezentate de variabile. Nu există containere de date. Datele nu sunt atribuite unei variabile. Unele valori pot fi definite și atribuite. Cu toate acestea, în majoritatea cazurilor acestea sunt funcții atribuite "variabilelor". Am pus "variabile" între citate, deoarece în programarea funcțională sunt imuabil. Chiar dacă cele mai multe limbi de programare funcționale nu impun immutabilitate, în același mod în care majoritatea limbilor orientate spre obiecte nu aplică obiecte, dacă schimbați valoarea după o sarcină pe care nu o mai faceți programarea funcțională pură.

Deoarece nu aveți valori atribuite variabilelor, în programarea funcțională pe care o aveți nici un stat.

Din cauza lipsei de stat și a unor sarcini, în programarea funcțională funcțiile nu au nici un efect secundar. Și din cauza celor trei motive anterioare, funcțiile sunt întotdeauna previzibile. Acest lucru înseamnă că, dacă numiți o funcție cu aceiași parametri din nou și din nou ... veți avea întotdeauna același rezultat. Acesta este un avantaj imens față de programarea orientată pe obiecte și reduce foarte mult complexitatea aplicațiilor multi-filetate și multi-filetate.

Dar, dacă vrem să exprimăm totul în funcții, trebuie să putem să le atribuim parametrilor sau să le reîntoarcem de la alte funcții. Astfel, programarea funcțională necesită suport pentru funcții de ordin superior. Acest lucru înseamnă în principiu că o funcție poate fi atribuită unei "variabile", trimisă ca parametru unei alte funcții și returnată ca rezultat al unei funcții.

În cele din urmă, deoarece nu avem valori în variabile, în timp ce buclele sunt neobișnuite pentru programarea funcțională și sunt înlocuite cu recursivitate.


Arată-mi codul!

Destul vorbind și filosofia unei lecții. Să codificăm!

Configurați un proiect PHP în IDE sau într-un editor de cod preferat. Creați în el a „Teste“ pliant. Creați două fișiere: FunSets.php în dosarul proiectului și FunSetsTest.php în dosarul Teste. Vom crea o aplicație, cu teste, care va reprezenta conceptul de seturi.

În matematică, un set este o colecție de obiecte distincte, considerată un obiect în sine. (Wikipedia)

Asta inseamna practic ca seturile sunt o gramada de lucruri intr-un singur loc. Aceste seturi pot fi și se caracterizează prin operații matematice: uniuni, intersecții, diferențe etc. Și prin proprietăți acționabile precum: conține.

Limitările noastre de programare

Deci, să codificăm! Dar asteapta. Cum? Ei bine, pentru a respecta conceptele programării funcționale, va trebui să aplicăm următoarele restricții codului nostru:

  • Nu există misiuni. - Nu avem dreptul să atribuim valori variabilelor. Cu toate acestea, avem voie să atribuim funcții variabilelor.
  • Nicio stare mișcătoare. - Nu ni se permite, în cazul unei misiuni, să schimbăm valoarea acelei cesiuni. De asemenea, nu este permisă modificarea valorii oricărei variabile care a stabilit valoarea sa ca parametru pentru funcția curentă. Deci, nici o schimbare de parametri.
  • Nu în timp și pentru bucle. - Nu ni se permite să folosim comenzile "în timp" și "pentru" din PHP. Cu toate acestea, ne putem defini metoda proprie de a circula prin elementele unui set și de ao numi foreach / for / while.

Nicio limitare nu se aplică testelor. Din cauza naturii PHPUnit, vom folosi codul PHP orientat obiect clasic acolo. De asemenea, pentru a satisface mai bine testele noastre, vom împacheta întregul cod de producție într-o singură clasă.

Funcția de definire a setului

Dacă sunteți un programator condimentat, dar nu este familiarizat cu programarea funcțională, acum este momentul să nu mai gândiți așa cum faceți de obicei și să fiți gata să vă părăsiți zona de confort. Uitați de toate modurile anterioare de raționament despre o problemă și imaginați-vă toate funcțiile.

Funcția de definire a unui set este metoda "conține".

funcția conține ($ set, $ elem) return $ set ($ elem); 

OK ... Nu este așa de evident, așa că să vedem cum i-am folosi.

$ set = funcție ($ element) return true;; conține ($ set, 100);

Ei bine, asta explică ceva mai bine. Functia „Conține“ are doi parametri:

  • $ set - reprezintă un set definit ca o funcție.
  • $ elem - reprezintă un element definit ca o valoare.

În acest context, toate astea „Conține“ trebuie să faceți este să aplicați funcția în $ set cu parametrul $ elem. Să o înfășurăm pe toate într-un test.

clasa FunSetsTest extinde PHPUnit_Framework_TestCase private $ funSets; funcția protejată setUp () $ this-> funSets = FunSets noi ();  test functionContainsIsImplemented () // Noi caracterizam un set prin care contine functia. Este funcția de bază a unui set. $ set = funcție ($ element) return true;; $ this-> assertTrue ($ this-> funSets-> conține ($ set, 100)); 

Și înfășurați codul nostru de producție înăuntru FunSets.php într-o clasă:

clasa FunSets funcția publică conține ($ set, $ elem) return $ set ($ elem); 

Puteți executa de fapt acest test și acesta va trece. Setul pe care l-am definit pentru acest test este doar o funcție care întotdeauna revine la adevărat. Este un "set adevărat".

Setul Singleton

Dacă capitolul precedent a fost puțin confuz sau arătat inutil în logică, acesta o va clarifica puțin. Vrem să definim un set cu un singur element, un set singleton. Rețineți că aceasta trebuie să fie o funcție și că vom dori să o folosim ca în testul de mai jos.

function testSingletonSetContainsSingleElement () // Un set singleton este caracterizat printr-o functie care a trecut la continutul va reveni valabil pentru un singur element // trecut ca parametru. Cu alte cuvinte, un singleton este un set cu un singur element. $ singleton = $ this-> funSets-> singletonSet (1); $ this-> assertTrue ($ this-> funSets-> conține ($ singleton, 1)); 

Trebuie să definim o funcție numită "SingeltonSet" cu un parametru care reprezintă un element al setului. În test, aceasta este numărul unu (1). Apoi ne așteptăm pe noi conține atunci când este chemat cu o funcție singleton, să se întoarcă Adevărat dacă parametrul trimis este egal cu unul. Codul care face testul este următorul:

functie publica singletonSet ($ elem) return function ($ otherElem) foloseste ($ elem) return $ elem == $ otherElem; ; 

Wow! Asta e o nebunie. Deci, funcția "SingletonSet" devine ca parametru un element ca $ elem. Apoi returnează o altă funcție care are un parametru $ otherElem iar această a doua funcție se va compara $ elem la $ otherElem.

Deci, cum funcționează acest lucru? În primul rând, această linie:

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

este transformat în ceea ce "SingletonSet (1)" se intoarce:

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

Atunci "conține ($ singleton, 1)" se numește. Care, la rândul său, numește tot ce este $ Singleton. Deci, codul devine:

$ Singleton (1)

Care execută de fapt codul în el cu $ otherElem având valoarea unu.

retur 1 == 1

Care este, desigur, adevărat și trece testul nostru.

Zâmbești deja? Crezi că creierul tău începe să fiarbă? Sigur că am făcut-o când am scris acest exemplu pentru Scala și am făcut din nou când am scris acest exemplu pentru prima dată în PHP. Cred că este extraordinar. Am reușit să definim un set, cu un element, cu capacitatea de a verifica dacă acesta conține valoarea pe care am trecut-o. Am făcut toate acestea fără o singură cesiune de valoare. Nu avem nici o variabilă care să conțină valoarea sau să aibă o stare de una. Nici o stare, nici o sarcină, nici o mutabilitate, nici o bucle. Suntem pe calea cea bună aici.


Uniune de seturi

Acum, că putem crea un set cu o singură valoare, trebuie să putem crea un set cu mai multe valori. Modul evident de a face acest lucru este definirea operațiunii sindicale pe seturile noastre. Unirea a două seturi singleton va reprezenta o altă uniune cu ambele valori. Vreau să faceți o clipă și să vă gândiți la soluție înainte de a vă deplasa la cod, poate să faceți o apreciere a testelor de mai jos.

funcția testUnionContainsAllElements () // O uniune este caracterizată de o funcție care primește 2 seturi ca parametri și conține toate seturile furnizate // Putem crea doar singletons în acest moment, așa că noi creăm 2 singletons și le unim $ s1 = $ this -> funSets-> singletonSet (1); $ s2 = $ acest-> funSets-> singletonSet (2); $ union = $ this-> funSets-> uniune ($ s1, $ s2); // Acum, verificați că atât 1, cât și 2 fac parte din sindicatul $ this-> assertTrue ($ this-> funSets-> conține ($ union, 1)); $ this-> assertTrue ($ this-> funSets-> conține ($ union, 2)); // ... și că nu conține 3 $ this-> assertFalse ($ this-> funSets-> conține ($ union, 3)); 

Vrem o funcție numită "uniune" care are doi parametri, ambele seturi. Amintiți-vă, seturile sunt doar funcții pentru noi, deci pentru noi "uniune" funcția va primi două funcții ca parametri. Apoi, vrem să putem verifica „Conține“ dacă uniunea conține un element sau nu. Deci, ale noastre "uniune" funcția trebuie să returneze o altă funcție „Conține“ poate utiliza.

($ s1, $ s2) funcția de returnare ($ otherElem) utilizează ($ s1, $ s2) retur $ this-> conține ($ s1, $ otherElem) || $ this-> conține ($ s2, $ otherElem); ; 

Aceasta funcționează destul de bine. Și este perfect valabil chiar și atunci când unirea voastră este chemată cu o altă uniune plus un singurton. Se cheamă conține în sine pentru fiecare parametru. Dacă este o uniune, ea va recurge. Este atat de simplu.


Intersecte și diferențe

Putem aplica aceeași logică a linerului cu modificări minore pentru a obține următoarele două funcții importante care caracterizează un set: intersecția - conține doar elementele comune între două seturi - și diferența - conține numai acele elemente din primul set care nu fac parte din al doilea set.

($ s1, $ s2) $ s1, $ s2) funcția de returnare ($ otherElem) utilizează ($ s1, $ s2) return $ this-> conține ($ s1, $ otherElem) && $ this-> otherElem); ;  funcția publică funcțională ($ s1, $ s2) funcția de returnare ($ otherElem) utilizează ($ s1, $ s2) return $ this-> conține ($ s1, $ otherElem) &&! $ this-> , $ otherElem); ; 

Nu vă voi inunda codul de testare pentru aceste două metode. Testele sunt scrise și le poți verifica dacă te uiți în codul atașat.


Setul de filtre

Ei bine, acest lucru este un pic mai complicat, nu vom putea rezolva acest lucru cu o singură linie de cod. Un filtru este o funcție care utilizează doi parametri: un set și o funcție de filtrare. Aplică funcția de filtrare la un set și returnează un alt set care conține numai elementele care satisfac funcția de filtrare. Pentru a înțelege mai bine, aici este testul pentru aceasta.

funcția testFilterContainsOnlyElementsThatMatchConditionFunction () $ u12 = $ this-> createUnionWithElements (1, 2); $ u123 = $ this-> funSets-> uniune ($ u12, $ this-> funSets-> singletonSet (3)); // Regulă de filtrare, găsiți elemente mai mari de 1 (adică 2 și 3) $ condition = function ($ elem) return $ elem> 1;; // set filtrat $ filteredSet = $ this-> funSets-> filter ($ u123, $ condition); // Verificați că setul filtrat nu conține 1 $ this-> assertFalse ($ this-> funSets-> conține ($ filteredSet, 1), "Nu trebuie să conțină 1"); // Verificați că conține 2 și 3 $ this-> assertTrue ($ this-> funSets-> conține ($ filteredSet, 2), "Trebuie să conțină 2"); $ this-> assertTrue ($ this-> funSets-> conține ($ filteredSet, 3), "ar trebui să conțină 3");  funcția privată createUnionWithElements ($ elem1, $ elem2) $ s1 = $ this-> funSets-> singletonSet ($ elem1); $ s2 = $ acest-> funSets-> singletonSet ($ elem2); returnează $ this-> funSets-> union ($ s1, $ s2); 

Creăm un set cu trei elemente: 1, 2, 3. Și îl plasăm în variabilă $ u123 astfel încât este ușor de identificat în testele noastre. Apoi definim o funcție pe care dorim să o aplicăm și o punem în test Stare $. În cele din urmă, numim filtru pe site-ul nostru $ u123 setat cu Stare $ și plasați setul rezultat în $ filteredSet. Apoi vom avea afirmații cu „Conține“ pentru a determina dacă setul arată cum vrem. Funcția noastră de stare este simplă, va reveni la adevărat dacă elementul este mai mare decât unul. Deci setul nostru final ar trebui să conțină numai valorile doi și trei, și asta verificăm în afirmațiile noastre.

funcția de returnare a funcției return ($ otherElem) ($ set, $ condition) if ($ condition ($ otherElem)) returnează $ this-> conține ($ set, $ otherElem); return false; ; 

Și aici te duci. Am implementat filtrarea cu doar trei linii de cod. Mai precis, dacă condiția se aplică elementului furnizat, executăm un conținut pe set pentru acel element. Dacă nu, tocmai ne întoarcem fals. Asta e.


Împușcarea peste elemente

Următorul pas este de a crea diferite funcții de buclă. Primul, "pentru toți()", va lua a $ set și a Stare $ și întoarcere Adevărat dacă Stare $ se aplică tuturor elementelor din $ set. Aceasta conduce la următorul test:

funcția testForAllCorrectlyTellsIfAllElementsSatisfyCondition () $ u123 = $ this-> createUnionWith123 (); $ higherThanZero = funcție ($ elem) întoarcere $ elem> 0; ; $ higherThanOne = funcție ($ elem) return $ elem> 1; ; $ higherThanTwo = funcție ($ elem) întoarcere $ elem> 2; ; $ this-> assertTrue ($ acest-> funSets-> forall ($ u123, $ higherThanZero)); $ this-> assertFalse ($ acest-> funSets-> forall ($ u123, $ higherThanOne)); $ this-> assertFalse ($ acest-> funSets-> forall ($ u123, $ higherThanTwo)); 

Am extras-o $ u123 crearea de la testul anterior într-o metodă privată. Apoi definim trei condiții diferite: mai mari decât zero, mai mari decât una și mai mari decât două. Deoarece setul nostru conține numerele unu, doi și trei, numai condiția mai mare decât zero ar trebui să se întoarcă la adevărat, restul ar trebui să fie fals. Într-adevăr, putem trece testul cu ajutorul unei alte metode recursive care se utilizează pentru a itera peste toate elementele.

privat $ bound = 1000; funcția privată pentruallIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) returnează true; alt $ ($ this-> contains ($ set, $ currentValue)) returnează $ condition ($ currentValue) && $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); altul returnează $ this-> forallIterator ($ currentValue + 1, $ set, $ condition);  funcția publică forall ($ set, $ condition) retur $ this-> forallIterator (- $ this-> bound, $ set, $ condition); 

Începem prin definirea unor limite pentru setul nostru. Valorile trebuie să fie între -1000 și +1000. Aceasta este o limitare rezonabilă pe care o impunem pentru a păstra acest exemplu destul de simplu. Functia "pentru toți" va apela metoda privată "ForallIterator" cu parametrii necesari pentru a decide în mod recursiv dacă toate elementele respectă starea. În această funcție, testăm mai întâi dacă suntem în afara limitelor. Dacă da, reveniți la adevărat. Apoi, verificați dacă setul nostru conține valoarea curentă și returnați valoarea curentă aplicată condiției împreună cu un logic "AND" cu un apel recursiv pentru noi înșine cu următoarea valoare. În caz contrar, trebuie doar să ne numim cu următoarea valoare și să returnăm rezultatul.

Acest lucru funcționează foarte bine, îl putem implementa în același mod ca și "Există ()". Aceasta se întoarce Adevărat dacă oricare dintre ele îndeplinește condiția.

există o funcție privatăIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) return false; elseif ($ this-> conține ($ set, $ currentValue)) returnează $ condition ($ currentValue) || $ this-> existsIterator ($ curentValue + 1, set $, condiție $); altul returnează $ this-> existsIterator ($ currentValue + 1, $ set, $ condition);  funcția publică există ($ set, $ condition) return $ this-> existsIterator (- $ this-> bound, $ set, $ condition); 

Singura diferență este că ne întoarcem fals atunci când ne aflăm în afara limitelor și folosim "OR" în loc de "ȘI" în a doua, dacă.

Acum, "Hartă()" vor fi diferite, mai simple și mai scurte.

($ set, $ action) $ setE, $ action) $ setE, $ action) $ setE, $ action)  returnați $ currentElem == $ action ($ elem);); ; 

Cartografierea înseamnă că aplicăm o acțiune tuturor elementelor unui set. Pentru hartă, nu avem nevoie de iterator ajutător, putem reutiliza "Există ()" și returnează acele elemente ale "există" care satisfac rezultatul acțiune $ aplicat $ element de. Acest lucru poate să nu fie evident la primul loc, așa că să vedem ce se întâmplă.

  • Am trimis setul 1, 2 și acțiunea element $ * 2 (dublu) pentru a cartografia.
  • Acesta va returna o funcție, evident, care are un parametru ca element și folosește setul și acțiunea de la un nivel superior.
  • Această funcție va apela există cu setul 1, 2 și funcția condiție $ currentElement este egală $ elem * 2.
  • există () va itera peste toate elementele între -1000 și +1000, limitele noastre. Când găsește un element, dublu din ceea ce vine de la „Conține“ (valoarea a $ currentElement) se va intoarce Adevărat.
  • Cu alte cuvinte, ultima comparație va reveni Adevărat pentru că apelul conține două valori, atunci când valoarea curentului înmulțită cu două, are două. Deci, pentru primul element al setului, unul, se va întoarce pe două. Pentru cel de-al doilea element, două, la valoarea patru.

Un exemplu practic

Programarea funcțională este distractivă dar este departe de a fi ideală în PHP. Deci, nu vă recomand să scrieți aplicații întregi în acest fel. Cu toate acestea, acum că ați învățat ce poate face PHP funcțional, puteți aplica părți din aceste cunoștințe în proiectele zilnice. Iată un exemplu de modul de autentificare. AuthPlugin clasa oferă o metodă care primește un utilizator și o parolă și face magia să autentifice utilizatorul și să-și stabilească permisiunile.

clasa AuthPlugin private $ permissions = array (); funcția de autentificare ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ adminModules = noi AdminModule (); $ this-> permisiuni [] = $ adminModules-> allowRead ($ username); $ this-> permisiuni [] = $ adminModules-> allowWrite ($ username); $ this-> permisiuni [] = $ adminModules-> allowExecute ($ username);  funcția privată verifyUser ($ username, $ password) // ... UTILIZATOR / PASS CHECKING // ... DETALII DE UTILIZARE A UTILIZATORULUI, ETC. 

Acum, s-ar putea să sune bine, dar are o mare problemă. 80% din "autentifica()" metoda folosește informații de la "AdminModules". Aceasta creează o dependență foarte puternică.


Ar fi mult mai rezonabil să luați cele trei apeluri și să creați o singură metodă AdminModules.


Deci, prin mutarea generației AdminModules am reușit să reducem trei dependențe la doar unul. Interfața publică a AdminModules a fost de asemenea redusă de la trei la o singură metodă. Cu toate acestea, nu suntem încă acolo. AuthPlugin încă direct depinde de AdminModules.

O abordare orientată pe obiecte

Dacă doriți ca pluginul nostru de autentificare să fie utilizat de orice modul, trebuie să definim o interfață comună pentru aceste module. Să injectăm dependența și să introducem o interfață.

clasa AuthPlugin private $ permissions = array (); privat $ appModule; funcția __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  autentifică funcția ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = array_merge ($ this-> permisiuni, $ this-> appModule-> getPermissions ($ username));  funcția privată verifyUser ($ username, $ password) // ... UTILIZATOR / PASS CHECKING // ... DETALII DE UTILIZARE A UTILIZATORULUI, ETC. 

AuthPlugin a primit un constructor. Ea devine un parametru de tip ApplicationModule, o interfață și apeluri "getPermissions ()" pe acest obiect injectat.

interfață ApplicationModule funcția publică getPermissions ($ username); 

ApplicationModule definește o singură metodă publică, "getPermissions ()", cu un nume de utilizator ca parametru.

class AdminModules implementează ApplicationModule // [...]

In cele din urma, AdminModules trebuie să pună în aplicare ApplicationModule interfață.


Acum, este mult mai bine. Al nostru AuthPlugin depinde numai de o interfață. AdminModules depinde de aceeași interfață, astfel încât AuthPlugin a devenit modul agnostic. Putem crea orice număr de module, toate punerea în aplicare ApplicationModule, și AuthPlugin va fi capabil să lucreze cu toți.

O abordare funcțională

O altă modalitate de a inversa dependența și de a face AdminModule, sau orice alt modul, pentru a utiliza AuthPlugin este de a injecta în aceste module o dependență de AuthPlugin. AuthPlugin va oferi o modalitate de a seta funcția de autentificare și fiecare aplicație va trimite în propriile sale "GetPermission ()" funcţie.

clasa AdminModules private $ authPlugin; funcția __construct (Authentitcation $ authPlugin) $ this-> authPlugin = $ authPlugin;  funcția privată allowRead ($ username) return "yes";  funcția privată allowWrite ($ username) return "no";  funcția privată allowExecute ($ username) return $ username == "joe"? "da nu";  funcția privată authenticate () $ this-> authPlugin-> setPermissions (funcția ($ username) $ permissions = array (); $ permissions [] = $ this-> allowRead ($ username); acest lucru-> allowWrite ($ username); $ permissions [] = $ this-> allowExecute ($ username); return $ permisiuni;); $ This-> authPlugin-> autentifice (); 

Începem cu AdminModule. Nu mai pune în aplicare nimic. Cu toate acestea, utilizează un obiect injectat care trebuie să implementeze autentificarea. În AdminModule va exista un "autentifica()" metoda care va suna "setPermissions ()" pe AuthPlugin și treci în funcția care trebuie utilizată.

interfata Autentificare function setPermissions ($ permissionGrantingFunction); funcția authenticate (); 

Interfața de autentificare definește pur și simplu cele două metode.

class AuthPlugin implementează autentificarea private $ permissions = array (); privat $ appModule; privat $ permissionsFunction; funcția __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  autentifică funcția ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = $ this-> permissionsFunction ($ username);  funcția privată verifyUser ($ username, $ password) // ... UTILIZATOR / PASS CHECKING // ... DETALII DE UTILIZARE A UTILIZATORULUI, ETC.  funcția publică setPermissions ($ permissionGrantingFunction) $ this-> permissionsFunction = $ permissionGrantingFunction; 

In cele din urma, AuthPlugin implementează autentificarea și stabilește funcția de intrare într-un atribut de clasă privată. Atunci, "autentificare()" devine o metodă proastă. Apel doar la funcție și apoi stabilește valoarea returnată. Este complet decuplat de orice se întâmplă.


Dacă ne uităm la schemă, există două schimbări importante:

  • In loc de AdminModule, AuthPlugin este cea care implementează interfața.
  • AuthPlugin va "apela înapoi" AdminModule, sau orice alt modul trimis în funcția de permisiuni.

Care dintre ele trebuie folosit?

Nu există un răspuns corect la această întrebare. Aș susține că, dacă procesul de determinare a permisiunilor este destul de dependent de modulul de aplicare, atunci abordarea orientată spre obiect este mai potrivită. Cu toate acestea, dacă credeți că fiecare modul de aplicație ar trebui să poată furniza o funcție de autentificare, și dvs. AuthPlugin este doar un schelet care oferă funcționalitatea de autentificare, dar fără să știe nimic despre utilizatori și proceduri, atunci puteți merge cu abordarea funcțională.

Abordarea funcțională vă face AuthPlugin foarte abstractă și puteți depinde de ea. Cu toate acestea, dacă intenționați să vă permiteți AuthPlugin să faceți mai mult și să aflați mai multe despre utilizatori și sistemul dvs., atunci acesta va deveni prea concret și nu doriți să vă depindeți de el. În acest caz, alegeți modul orientat spre obiect și lăsați betonul AuthPlugin depind de modulele de aplicație mai abstracte.

Cod