Programmazione funzionale in PHP

Il nuovo clamore nella programmazione riguarda i paradigmi di programmazione funzionale. I linguaggi funzionali sono sempre più utilizzati in applicazioni migliori e migliori. Scala, Haskel, ecc. Sono fiorenti e altri linguaggi più conservativi come Java hanno iniziato ad adottare alcuni dei paradigmi di programmazione funzionale (vedi chiusure in Java7 e eval pigro per gli elenchi in Java8). Tuttavia, quello che solo pochi sanno è che PHP è abbastanza versatile quando si tratta di programmazione funzionale. Tutti i principali concetti di programmazione funzionale possono essere espressi in PHP. Quindi, se sei nuovo alla programmazione funzionale, preparati a farti saltare la mente, e se hai già familiarità con la programmazione funzionale, preparati a divertirti molto con questo tutorial.


Paradigmi di programmazione

Senza alcun paradigma di programmazione potremmo fare ciò che vogliamo in qualsiasi modo vogliamo. Mentre ciò porterebbe ad un'estrema flessibilità, porterebbe anche a architetture impossibili e codice molto gonfiato. Quindi, i paradigmi di programmazione sono stati inventati per aiutarci, programmatori, a pensare in modo specifico a un programma specifico e in questo modo, limitare la nostra capacità di esprimere la nostra soluzione.

Ogni paradigma di programmazione ci toglie la libertà:

  • Programmazione modulare porta via la dimensione del programma illimitata.
  • Programmazione strutturata e procedurale stanno portando via il "go-to" e limita il programmatore alla sequenza, selezione e iterazione.
  • Programmazione orientata agli oggetti toglie i puntatori alle funzioni.
  • Programmazione funzionale toglie incarico e stato mutabile.

Principi di programmazione funzionale

Nella programmazione funzionale, non ci sono dati rappresentati da variabili.

Nella programmazione funzionale tutto è una funzione. E intendo tutto. Ad esempio un set, come in matematica, può essere rappresentato come diverse funzioni. Una matrice o lista è anche una funzione o un gruppo di funzioni.

Nella programmazione orientata agli oggetti, tutto è un oggetto. E un oggetto è una raccolta di dati e metodi che eseguono azioni su quei dati. Gli oggetti hanno uno stato, uno stato mutevole e mutevole.

Nella programmazione funzionale, non ci sono dati rappresentati da variabili. Non ci sono contenitori di dati. I dati non sono assegnati a una variabile. Alcuni valori possono essere definiti e assegnati. Tuttavia, nella maggior parte dei casi sono funzioni assegnate a "variabili". Ho messo "variabili" tra virgolette perché sono in programmazione funzionale immutabile. Anche se la maggior parte dei linguaggi di programmazione funzionale non impone l'immutabilità, allo stesso modo la maggior parte dei linguaggi orientati agli oggetti non impone l'applicazione di oggetti, se modifichi il valore dopo un compito non esegui più la pura programmazione funzionale.

Perché non hai valori assegnati alle variabili, nella programmazione funzionale che hai nessuno stato.

A causa di nessuno stato e nessun incarico, nella programmazione funzionale le funzioni non hanno alcun effetto collaterale. E a causa dei tre precedenti motivi, le funzioni sono sempre prevedibili. Ciò significa che se si chiama una funzione con gli stessi parametri ancora e ancora e ancora ... si avrà sempre lo stesso risultato. Questo è un enorme vantaggio rispetto alla programmazione orientata agli oggetti e riduce notevolmente la complessità delle applicazioni multi-threaded e massivamente multi-threaded.

Ma, se vogliamo esprimere tutto nelle funzioni, dobbiamo essere in grado di assegnarle ai parametri o di restituirle da altre funzioni. Quindi la programmazione funzionale richiede il supporto per funzioni di ordine superiore. Ciò significa fondamentalmente che una funzione può essere assegnata a una "variabile", inviata come parametro a un'altra funzione e restituita come risultato di una funzione.

Infine, poiché non abbiamo valori nelle variabili, while e for loops sono insoliti per la programmazione funzionale e vengono sostituiti con la ricorsione.


Mostrami il codice!

Basta parlare e filosofia per una lezione. Facciamo codice!

Imposta un progetto PHP nel tuo IDE o editor di codice preferito. Crea in esso a "Test" cartella. Crea due file: FunSets.php nella cartella del progetto, e FunSetsTest.php nella cartella Tests. Creeremo un'applicazione, con test, che rappresenterà il concetto di set.

In matematica, un insieme è una raccolta di oggetti distinti, considerati come un oggetto a sé stante. (Wikipedia)

Ciò significa fondamentalmente che i set sono un mucchio di cose in un singolo posto. Questi insiemi possono essere e sono caratterizzati da operazioni matematiche: unioni, intersezioni, differenze, ecc. E da proprietà azionabili come: contiene.

I nostri limiti di programmazione

Quindi diciamo il codice! Ma aspetta. Come? Bene, per rispettare i concetti di programmazione funzionale dovremo applicare le seguenti restrizioni al nostro codice:

  • Nessun incarico. - Non è consentito assegnare valori alle variabili. Tuttavia, è consentito assegnare funzioni a variabili.
  • Nessuno stato mutabile. - Non è consentito, in caso di assegnazione, modificare il valore di tale assegnazione. Inoltre, non è consentito modificare il valore di alcuna variabile che ha il suo valore impostato come parametro per la funzione corrente. Quindi, nessuna modifica dei parametri.
  • No while and for loops. - Non è consentito utilizzare i comandi "while" e "for" di PHP. Noi, tuttavia, possiamo definire il nostro metodo per scorrere gli elementi di un insieme e chiamarlo foreach / for / while.

Nessuna limitazione si applica ai test. A causa della natura di PHPUnit, utilizzeremo il codice PHP classico orientato agli oggetti. Inoltre, per meglio adattarsi ai nostri test, avvolgeremo tutto il nostro codice di produzione in una singola classe.

La funzione di definizione del set

Se sei un programmatore esperto, ma non hai familiarità con la programmazione funzionale, ora è il momento di smettere di pensare come fai di solito e sii pronto a lasciare la tua zona di comfort. Dimentica tutti i tuoi precedenti modi di ragionare su un problema e immagina tutte le funzioni.

La funzione di definizione di un insieme è il suo metodo "contiene".

la funzione contiene ($ set, $ elem) return $ set ($ elem); 

OK ... Non è così ovvio, quindi vediamo come utilizzarlo.

$ set = function ($ element) return true;; contiene ($ set, 100);

Bene, questo lo spiega un po 'meglio. La funzione "Contiene" ha due parametri:

  • $ set - rappresenta un insieme definito come una funzione.
  • $ elem - rappresenta un elemento definito come un valore.

In questo contesto, tutto questo "Contiene" deve fare è applicare la funzione in $ set con il parametro $ elem. Completiamo tutto in un test.

la classe FunSetsTest estende PHPUnit_Framework_TestCase private $ funSets; funzione protetta setUp () $ this-> funSets = new FunSets ();  function testContainsIsImplemented () // Caratterizziamo un set tramite la sua funzione contiene. È la funzione di base di un set. $ set = function ($ element) return true;; $ this-> assertTrue ($ this-> funSets-> contains ($ set, 100)); 

E avvolgi il nostro codice di produzione all'interno FunSets.php in una classe:

classe FunSets contiene la funzione pubblica ($ set, $ elem) return $ set ($ elem); 

In realtà puoi eseguire questo test e passerà. Il set che abbiamo definito per questo test è solo una funzione che restituisce sempre true. È un "vero set".

The Singleton Set

Se il capitolo precedente era un po 'confuso o sembrava inutile nella logica, questo lo chiarirebbe un po'. Vogliamo definire un set con un singolo elemento, un insieme singleton. Ricorda, questa deve essere una funzione, e vorremmo usarla come nel test qui sotto.

function testSingletonSetContainsSingleElement () // Un set singleton è caratterizzato da una funzione che passata a contiene restituirà true per il singolo elemento // passato come parametro. In altre parole, un singleton è un insieme con un singolo elemento. $ singleton = $ this-> funSets-> singletonSet (1); $ this-> assertTrue ($ this-> funSets-> contains ($ singleton, 1)); 

Abbiamo bisogno di definire una funzione chiamata "SingeltonSet" con un parametro che rappresenta un elemento dell'insieme. Nel test, questo è il numero uno (1). Quindi, ci aspettiamo il nostro contiene metodo, quando chiamato con una funzione singleton, per tornare vero se il parametro inviato è uguale a uno. Il codice che esegue il test pass è il seguente:

funzione pubblica singletonSet ($ elem) return function ($ otherElem) use ($ elem) return $ elem == $ otherElem; ; 

Wow! Questo è pazzesco. Quindi, la funzione "SingletonSet" ottiene come parametro un elemento come $ elem. Quindi restituisce un'altra funzione che ha un parametro $ otherElem e questa seconda funzione si confronterà $ elem a $ otherElem.

Quindi, come funziona? In primo luogo, questa linea:

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

si trasforma in cosa "SingletonSet (1)" ritorna:

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

Poi "contiene ($ singleton, 1)" è chiamato. Che, a sua volta, chiama qualunque cosa sia $ Singleton. Quindi il codice diventa:

$ Singleton (1)

Che in realtà esegue il codice con $ otherElem avendo il valore uno.

return 1 == 1

Che è ovviamente vero e il nostro test passa.

Stai già sorridendo? Senti il ​​cervello iniziare a bollire? Ho certamente fatto quando ho scritto questo esempio per la prima volta in Scala e l'ho fatto di nuovo quando ho scritto questo esempio in PHP. Penso che sia straordinario. Siamo riusciti a definire un set, con un elemento, con la possibilità di verificare che contenga il valore che gli abbiamo passato. Abbiamo fatto tutto questo senza un singolo incarico di valore. Non abbiamo alcuna variabile contenente il valore uno o lo stato di uno. Nessuno stato, nessun compito, nessuna mutevolezza, nessun loop. Siamo sulla strada giusta qui.


Unione di insiemi

Ora che possiamo creare un set con un singolo valore, dobbiamo essere in grado di creare un set con diversi valori. Il modo più ovvio per farlo è definire l'operazione sindacale sui nostri set. L'unione di due set singleton rappresenterà un'altra unione con entrambi i valori. Voglio che ti prenda un minuto e pensi alla soluzione prima di passare al codice, magari fare un picco sui test qui sotto.

function testUnionContainsAllElements () // Un union è caratterizzato da una funzione che ottiene 2 set come parametri e contiene tutti gli insiemi forniti // Possiamo creare solo singleton a questo punto, quindi creiamo 2 singleton e li uniamo $ s1 = $ this -> funSets-> singletonSet (1); $ s2 = $ this-> funSets-> singletonSet (2); $ union = $ this-> funSets-> union ($ s1, $ s2); // Ora, controlla che sia 1 che 2 facciano parte del gruppo $ this-> assertTrue ($ this-> funSets-> contains ($ union, 1)); $ this-> assertTrue ($ this-> funSets-> contains ($ union, 2)); // ... e che non contenga 3 $ this-> assertFalse ($ this-> funSets-> contains ($ union, 3)); 

Vogliamo una funzione chiamata "unione" che ottiene due parametri, entrambi i set. Ricorda, gli insiemi sono solo funzioni per noi, quindi i nostri "unione" la funzione avrà due funzioni come parametri. Quindi, vogliamo essere in grado di verificare "Contiene" se l'unione contiene un elemento o no. Quindi, il nostro "unione" la funzione deve restituire un'altra funzione "Contiene" poter usare.

unione funzione pubblica ($ s1, $ s2) funzione return ($ otherElem) use ($ s1, $ s2) return $ this-> contains ($ s1, $ otherElem) || $ this-> contains ($ s2, $ otherElem); ; 

In realtà funziona abbastanza bene. Ed è perfettamente valido anche quando il tuo sindacato viene chiamato con un'altra unione più un singleton. Chiama contiene dentro se stesso per ogni parametro. Se si tratta di un sindacato, si reciterà. È così semplice.


Interseca e Differenza

Possiamo applicare la stessa logica del liner con modifiche minori per ottenere le due funzioni più importanti che caratterizzano un set: intersezione - contiene solo gli elementi comuni tra due insiemi - e differenza - contiene solo quegli elementi del primo set che non fanno parte del secondo set.

intersecano la funzione pubblica ($ s1, $ s2) restituisce la funzione ($ otherElem) usa ($ s1, $ s2) restituisce $ this-> contains ($ s1, $ otherElem) && $ this-> contains ($ s2, $ otherElem); ;  public function diff ($ s1, $ s2) return function ($ otherElem) use ($ s1, $ s2) return $ this-> contains ($ s1, $ otherElem) &&! $ this-> contains ($ s2 , $ otherElem); ; 

Non ti inonderò con il codice di prova per questi due metodi. I test sono scritti e puoi controllarli se cerchi il codice allegato.


Set di filtri

Bene, questo è un po 'più complicato, non saremo in grado di risolverlo con una singola riga di codice. Un filtro è una funzione che utilizza due parametri: un set e una funzione di filtro. Applica la funzione di filtro a un set e restituisce un altro set che contiene solo gli elementi che soddisfano la funzione di filtro. Per capirlo meglio, ecco il test per questo.

function testFilterContainsOnlyElementsThatMatchConditionFunction () $ u12 = $ this-> createUnionWithElements (1, 2); $ u123 = $ this-> funSets-> union ($ u12, $ this-> funSets-> singletonSet (3)); // Filtra la regola, trova elementi maggiori di 1 (ovvero 2 e 3) $ condizione = funzione ($ elem) return $ elem> 1;; // set filtrato $ filteredSet = $ this-> funSets-> filter ($ u123, $ condition); // Verifica che il set filtrato non contenga 1 $ this-> assertFalse ($ this-> funSets-> contains ($ filteredSet, 1), "Should not contain 1"); // Controlla che contenga 2 e 3 $ this-> assertTrue ($ this-> funSets-> contains ($ filteredSet, 2), "Should contain 2"); $ this-> assertTrue ($ this-> funSets-> contains ($ filteredSet, 3), "Should contain 3");  funzione privata createUnionWithElements ($ elem1, $ elem2) $ s1 = $ this-> funSets-> singletonSet ($ elem1); $ s2 = $ this-> funSets-> singletonSet ($ elem2); restituire $ this-> funSets-> union ($ s1, $ s2); 

Creiamo un set con tre elementi: 1, 2, 3. E lo inseriamo nella variabile $ U123 quindi è facilmente identificabile nei nostri test. Quindi definiamo una funzione che vogliamo applicare al test e inserirla $ condizione. Infine, chiamiamo filtro sul nostro $ U123 impostato con $ condizione e inserire il set risultante in $ filteredSet. Quindi lanciamo asserzioni con "Contiene" per determinare se il set sembra come vogliamo. La nostra condizione è semplice, restituirà true se l'elemento è maggiore di uno. Quindi il nostro set finale dovrebbe contenere solo i valori due e tre, e questo è ciò che controlliamo nelle nostre asserzioni.

filtro funzione pubblica ($ set, $ condizione) funzione return ($ otherElem) usa ($ set, $ condizione) if ($ condition ($ otherElem)) restituisce $ this-> contains ($ set, $ otherElem); restituisce falso; ; 

E qui vai. Abbiamo implementato il filtraggio con solo tre righe di codice. Più precisamente, se la condizione si applica all'elemento fornito, eseguiamo una contiene sul set per quell'elemento. Se no, torniamo indietro falso. Questo è tutto.


In loop sugli elementi

Il prossimo passo è creare varie funzioni di loop. Il primo, "per tutti()", prenderà un $ set e a $ condizione e ritorno vero Se $ condizione si applica a tutti gli elementi del $ set. Questo porta al seguente test:

function testForAllCorrectlyTellsIfAllElementsSatisfyCondition () $ u123 = $ this-> createUnionWith123 (); $ higherThanZero = function ($ elem) return $ elem> 0; ; $ higherThanOne = function ($ elem) return $ elem> 1; ; $ higherThanTwo = function ($ 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)); 

Abbiamo estratto il $ U123 creazione dal test precedente in un metodo privato. Quindi definiamo tre diverse condizioni: superiore a zero, superiore a uno e superiore a due. Poiché il nostro set contiene i numeri uno, due e tre, solo la condizione più alta di zero dovrebbe restituire true, il resto dovrebbe essere falso. Infatti, possiamo far passare il test con l'aiuto di un altro metodo ricorsivo utilizzato per iterare su tutti gli elementi.

$ $ privato = 1000; funzione privata forallIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) restituisce true; elseif ($ this-> contains ($ set, $ currentValue)) restituisce $ condition ($ currentValue) && $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); altrimenti restituire $ this-> forallIterator ($ currentValue + 1, $ set, $ condition);  public function forall ($ set, $ condition) return $ this-> forallIterator (- $ this-> bound, $ set, $ condition); 

Iniziamo definendo alcuni limiti per il nostro set. I valori devono essere compresi tra -1000 e +1000. Questa è una ragionevole limitazione che imponiamo per mantenere questo esempio abbastanza semplice. La funzione "per tutti" chiamerà il metodo privato "ForallIterator" con i parametri necessari per decidere in modo ricorsivo se tutti gli elementi rispettano la condizione. In questa funzione, per prima cosa testiamo se siamo fuori limite. Se sì, restituisce true. Quindi controlla se il nostro insieme contiene il valore corrente e restituisce il valore corrente applicato alla condizione insieme a un "AND" logico con una chiamata ricorsiva a noi stessi con il valore successivo. Altrimenti, chiamaci con il valore successivo e restituisci il risultato.

Funziona bene, possiamo implementarlo nello stesso modo di "Esiste ()". Questo ritorna vero se uno qualsiasi degli elementi soddisfa la condizione.

funzione privata existsIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) return false; elseif ($ this-> contains ($ set, $ currentValue)) restituisce $ condition ($ currentValue) || $ this-> existsIterator ($ currentValue + 1, $ set, $ condition); else return $ this-> existsIterator ($ currentValue + 1, $ set, $ condition);  esiste una funzione pubblica ($ set, $ condizione) return $ this-> existsIterator (- $ this-> bound, $ set, $ condition); 

L'unica differenza è che torniamo falso quando fuori dai limiti e usiamo "OR" invece di "AND" nel secondo se.

Adesso, "carta geografica()" sarà diverso, più semplice e più breve.

mappa funzione pubblica ($ set, $ azione) funzione return ($ currentElem) use ($ set, $ action) return $ this-> exists ($ set, funzione ($ elem) use ($ currentElem, $ action)  return $ currentElem == $ action ($ elem);); ; 

Mappatura significa che applichiamo un'azione a tutti gli elementi di un insieme. Per la mappa, non abbiamo bisogno di un iteratore di supporto, possiamo riutilizzarlo "Esiste ()" e restituire quegli elementi di "esiste" che soddisfano il risultato di $ azione applicata ai $ elemento. Questo potrebbe non essere ovvio al primo sito, quindi vediamo cosa sta succedendo.

  • Inviamo il set 1, 2 e l'azione $ element * 2 (double) mappare.
  • Restituirà una funzione, ovviamente, che ha un parametro come elemento e utilizza l'insieme e l'azione da un livello più alto.
  • Questa funzione chiamerà esiste con il set 1, 2 e la funzione di condizione $ currentElement è uguale a $ elem * 2.
  • exists () itererà su tutti gli elementi tra -1000 e +1000, i nostri limiti. Quando trova un elemento, il doppio di ciò che viene da "Contiene" (il valore di $ currentElement) tornerà vero.
  • In altre parole, l'ultimo confronto tornerà vero per la chiamata a contiene con valore due, quando il valore della corrente moltiplicato per due, ne risulta in due. Quindi, per il primo elemento del set, uno, restituirà true su due. Per il secondo elemento, due, sul valore quattro.

Un esempio pratico

Bene, la programmazione funzionale è divertente ma non è l'ideale in PHP. Quindi, non ti consiglio di scrivere intere applicazioni in questo modo. Tuttavia, ora che hai imparato ciò che PHP può fare dal punto di vista funzionale, puoi applicare parti di questa conoscenza nei tuoi progetti quotidiani. Ecco un esempio di modulo di autenticazione. Il AuthPlugin class fornisce un metodo che riceve un utente e una password e fa la sua magia per autenticare l'utente e impostare le sue autorizzazioni.

class AuthPlugin private $ permissions = array (); funzione di autenticazione ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ adminModules = new AdminModules (); $ this-> permissions [] = $ adminModules-> allowRead ($ username); $ this-> permissions [] = $ adminModules-> allowWrite ($ username); $ this-> permissions [] = $ adminModules-> allowExecute ($ username);  funzione privata verifyUser ($ username, $ password) // ... DO USER / PASS CHECKING // ... CARICARE I DETTAGLI UTENTE, ECC. 

Ora, questo può sembrare OK, ma ha un grosso problema. 80% del "autenticare()" metodo usa informazioni dal "AdminModules". Questo crea una dipendenza molto forte.


Sarebbe molto più ragionevole prendere le tre chiamate e creare un unico metodo AdminModules.


Quindi, spostando la generazione in AdminModules siamo riusciti a ridurre tre dipendenze a una sola. L'interfaccia pubblica di AdminModules è stato anche ridotto da tre a un solo metodo. Tuttavia, non siamo ancora lì. AuthPlugin dipende ancora direttamente AdminModules.

Un approccio orientato agli oggetti

Se vuoi che il nostro plugin di autenticazione sia utilizzabile da qualsiasi modulo, dobbiamo definire un'interfaccia comune per questi moduli. Inseriamo la dipendenza e introduciamo un'interfaccia.

class AuthPlugin private $ permissions = array (); private $ appModule; function __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  funzione di autenticazione ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = array_merge ($ this-> permessi, $ this-> appModule-> getPermissions ($ username));  funzione privata verifyUser ($ username, $ password) // ... DO USER / PASS CHECKING // ... CARICARE I DETTAGLI UTENTE, ECC. 

AuthPlugin ho un costruttore Ottiene un parametro di tipo ApplicationModule, un'interfaccia e chiamate "GetPermissions ()" su questo oggetto iniettato.

interfaccia ApplicationModule public function getPermissions ($ username); 

ApplicationModule definisce un singolo metodo pubblico, "GetPermissions ()", con un nome utente come parametro.

classe AdminModules implementa ApplicationModule // [...]

Finalmente, AdminModules ha bisogno di implementare il ApplicationModule interfaccia.


Ora, questo è molto meglio. Nostro AuthPlugin dipende solo da un'interfaccia. AdminModules dipende dalla stessa interfaccia, quindi il AuthPlugin diventato modulo agnostico. Possiamo creare qualsiasi numero di moduli, tutti implementati ApplicationModule, e AuthPlugin sarà in grado di lavorare con tutti loro.

Un approccio funzionale

Un altro modo per invertire la dipendenza e fare AdminModule, o qualsiasi altro modulo, per usare il AuthPlugin è iniettare in questi moduli una dipendenza per AuthPlugin. AuthPlugin fornirà un modo per impostare la funzione di autenticazione e ogni applicazione invierà la propria "avere il permesso()" funzione.

classe AdminModules private $ authPlugin; function __construct (Authentitcation $ authPlugin) $ this-> authPlugin = $ authPlugin;  funzione privata allowRead ($ username) return "yes";  funzione privata allowWrite ($ username) return "no";  funzione privata allowExecute ($ username) return $ username == "joe"? "si No";  private function authenticate () $ this-> authPlugin-> setPermissions (function ($ username) $ permissions = array (); $ permessi [] = $ this-> allowRead ($ username); $ permessi [] = $ questo-> allowWrite ($ username); $ permessi [] = $ this-> allowExecute ($ username); restituisce $ permessi;); $ This-> authPlugin-> autenticare (); 

Iniziamo con AdminModule. Non implementa più nulla. Tuttavia, utilizza un oggetto iniettato che deve implementare l'autenticazione. Nel AdminModule ci sarà un "autenticare()" metodo che chiamerà "SetPermissions ()" sopra AuthPlugin e passare la funzione che deve essere utilizzata.

interfaccia Authentication function setPermissions ($ permissionGrantingFunction); funzione authenticate (); 

L'interfaccia di autenticazione definisce semplicemente i due metodi.

la classe AuthPlugin implementa l'autenticazione private $ permissions = array (); private $ appModule; private $ permissionsFunction; function __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  funzione di autenticazione ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = $ this-> permissionsFunction ($ username);  funzione privata verifyUser ($ username, $ password) // ... DO USER / PASS CHECKING // ... CARICARE I DETTAGLI UTENTE, ECC.  public function setPermissions ($ permissionGrantingFunction) $ this-> permissionsFunction = $ permissionGrantingFunction; 

Finalmente, AuthPlugin implementa l'autenticazione e imposta la funzione in entrata in un attributo di classe privato. Poi, "autenticazione()" diventa un metodo stupido. Chiama semplicemente la funzione e quindi imposta il valore di ritorno. È completamente disaccoppiato da qualunque cosa entri.


Se guardiamo allo schema, ci sono due importanti cambiamenti:

  • Invece di AdminModule, AuthPlugin è quello che implementa l'interfaccia.
  • AuthPlugin chiamerà "Call Back" AdminModule, o qualunque altro modulo inviato nella funzione di autorizzazioni.

Quale usare?

Non c'è una risposta corretta a questa domanda. Direi che se il processo di determinazione delle autorizzazioni è abbastanza dipendente dal modulo dell'applicazione, allora l'approccio orientato agli oggetti è più appropriato. Tuttavia, se pensi che ogni modulo applicativo debba essere in grado di fornire una funzione di autenticazione, e il tuo AuthPlugin è solo uno scheletro che fornisce la funzionalità di autenticazione, ma senza sapere nulla su utenti e procedure, quindi si può andare con l'approccio funzionale.

L'approccio funzionale rende il tuo AuthPlugin molto astratto e puoi dipendere da esso. Tuttavia, se prevedi di consentire il tuo AuthPlugin per fare di più e sapere di più sugli utenti e sul tuo sistema, allora diventerà troppo concreto e non vuoi dipendere da esso. In tal caso, scegli il modo orientato agli oggetti e lascia il calcestruzzo AuthPlugin dipende dai moduli applicativi più astratti.