Funktionale Programmierung in PHP

Beim neuen Programmierhype geht es um funktionale Programmierparadigmen. Funktionale Sprachen werden immer häufiger in größeren und besseren Anwendungen eingesetzt. Scala, Haskel usw. gedeihen und andere, konservativere Sprachen wie Java begannen, einige der funktionalen Programmierparadigmen zu übernehmen (siehe Abschlüsse in Java7 und Lazy Eval für Listen in Java8). Was jedoch nur wenige wissen, ist, dass PHP in Bezug auf die funktionale Programmierung sehr vielseitig ist. Alle wesentlichen funktionalen Programmierungskonzepte können in PHP ausgedrückt werden. Wenn Sie also mit der funktionalen Programmierung noch nicht vertraut sind, sollten Sie sich darauf vorbereiten, Ihre Gedanken durchzubringen, und wenn Sie bereits mit der funktionalen Programmierung vertraut sind, sollten Sie sich darauf einstellen, dass Sie mit diesem Tutorial großen Spaß haben werden.


Programmierparadigmen

Ohne Programmierparadigmen könnten wir alles tun, was wir wollen, wie auch immer wir wollen. Dies würde zwar zu extremer Flexibilität führen, aber auch zu unmöglichen Architekturen und sehr aufgeblähtem Code. Daher wurden Programmierparadigmen erfunden, um uns Programmierern dabei zu helfen, auf spezifische Weise über ein bestimmtes Programm nachzudenken und auf diese Weise unsere Fähigkeit einzuschränken, unsere Lösung auszudrücken.

Jedes Programmierparadigma nimmt uns die Freiheit:

  • Modulare Programmierung nimmt unbegrenzte Programmgröße weg.
  • Strukturierte und prozedurale Programmierung nehmen das "Go-to" weg und beschränken den Programmierer auf Sequenz, Auswahl und Iteration.
  • Objekt orientierte Programmierung entfernt Zeiger auf Funktionen.
  • Funktionale Programmierung nimmt Zuordnung und veränderlichen Zustand weg.

Funktionsprogrammierprinzipien

Bei der funktionalen Programmierung haben Sie keine Daten, die durch Variablen dargestellt werden.

In der funktionalen Programmierung alles ist eine funktion. Und ich meine alles. Beispielsweise kann ein Satz wie in der Mathematik als mehrere Funktionen dargestellt werden. Ein Array oder eine Liste ist auch eine Funktion oder eine Gruppe von Funktionen.

In der objektorientierten Programmierung ist alles ein Objekt. Ein Objekt ist eine Sammlung von Daten und Methoden, die mit diesen Daten Aktionen ausführen. Objekte haben einen Zustand, einen flüchtigen, veränderbaren Zustand.

Bei der funktionalen Programmierung haben Sie keine Daten, die durch Variablen dargestellt werden. Es gibt keine Datencontainer. Daten werden keiner Variablen zugewiesen. Einige Werte können definiert und zugewiesen werden. In den meisten Fällen handelt es sich jedoch um Funktionen, die "Variablen" zugewiesen sind. Ich habe "Variablen" zwischen Anführungszeichen gesetzt, weil sie in der funktionalen Programmierung sind unveränderlich. Obwohl die meisten funktionalen Programmiersprachen keine Unveränderlichkeit erzwingen, erzwingen die meisten objektorientierten Sprachen keine Objekte. Wenn Sie den Wert nach einer Zuweisung ändern, führen Sie keine rein funktionale Programmierung mehr durch.

Da Sie Variablen keine Werte zugewiesen haben, haben Sie in der funktionalen Programmierung entsprechende Werte kein Zustand.

Da keine Zustände und keine Zuordnungen vorhanden sind, bei der funktionalen Programmierung Funktionen haben keine Nebenwirkung. Und aus drei Gründen, Funktionen sind immer vorhersehbar. Das bedeutet im Wesentlichen, dass Sie immer, wenn Sie eine Funktion mit denselben Parametern immer wieder aufrufen, immer dasselbe Ergebnis haben. Dies ist ein großer Vorteil gegenüber der objektorientierten Programmierung und reduziert die Komplexität von Multithread- und Massive-Multithread-Anwendungen erheblich.

Wenn wir jedoch alles in Funktionen ausdrücken wollen, müssen wir sie Parametern zuweisen oder von anderen Funktionen zurückgeben können. Daher erfordert die funktionale Programmierung die Unterstützung von Funktionen höherer Ordnung. Dies bedeutet im Grunde, dass eine Funktion einer "Variablen" zugewiesen, als Parameter an eine andere Funktion gesendet und als Ergebnis einer Funktion zurückgegeben werden kann.

Schließlich, weil wir keine Werte in Variablen haben, sind while und for Schleifen für die funktionale Programmierung ungewöhnlich und werden durch Rekursion ersetzt.


Zeigen Sie mir den Code!

Genug geredet und Philosophie für eine Stunde. Lassen Sie uns Code schreiben!

Richten Sie ein PHP-Projekt in Ihrer bevorzugten IDE oder im Code-Editor ein. Erstellen Sie darin ein "Tests" Mappe. Erstellen Sie zwei Dateien: FunSets.php im Ordner des Projekts und FunSetsTest.php im Testordner. Wir erstellen eine Anwendung mit Tests, die das Konzept von Mengen darstellen.

In der Mathematik ist eine Menge eine Sammlung verschiedener Objekte, die als eigenständiges Objekt betrachtet werden. (Wikipedia)

Das bedeutet im Grunde, dass Sets eine Menge Dinge an einem einzigen Ort sind. Diese Mengen können und werden durch mathematische Operationen charakterisiert: Vereinigungen, Schnittpunkte, Unterschiede usw. Und durch umsetzbare Eigenschaften wie: enthält.

Unsere Programmierbeschränkungen

Also lasst uns Code schreiben! Aber warte. Wie? Nun, um die Konzepte der funktionalen Programmierung zu respektieren, müssen wir folgende Einschränkungen auf unseren Code anwenden:

  • Keine Zuordnungen. - Wir dürfen keinen Variablen Werte zuweisen. Wir können jedoch Variablen Variablen zuweisen.
  • Kein veränderlicher Zustand. - Es ist uns nicht gestattet, im Falle einer Abtretung den Wert dieser Abtretung zu ändern. Wir dürfen auch nicht den Wert einer Variablen ändern, deren Wert als Parameter für die aktuelle Funktion festgelegt wurde. Also keine Änderung der Parameter.
  • Nein, während und für Schleifen. - Wir dürfen keine PHP-Befehle "while" und "for" verwenden. Wir können jedoch unsere eigene Methode definieren, um die Elemente einer Menge zu durchlaufen und foreach / for / while zu nennen.

Für Tests gelten keine Einschränkungen. Aufgrund der Natur von PHPUnit verwenden wir dort klassischen objektorientierten PHP-Code. Um unsere Tests besser unterzubringen, werden wir unseren gesamten Produktionscode in einer einzigen Klasse zusammenfassen.

Die definierende Funktion des Sets

Wenn Sie ein erfahrener Programmierer sind, aber mit der funktionalen Programmierung nicht vertraut sind, Jetzt ist es an der Zeit, nicht mehr wie üblich zu denken und seien Sie bereit, Ihre Komfortzone zu verlassen. Vergessen Sie alle Ihre bisherigen Denkweisen über ein Problem und stellen Sie sich alle Funktionen vor.

Die definierende Funktion einer Menge ist ihre "Enthält" -Methode.

Funktion enthält ($ set, $ elem) return $ set ($ elem); 

OK… Das ist nicht so offensichtlich, also sehen wir uns an, wie wir es benutzen würden.

$ set = Funktion ($ Element) return true;; enthält ($ set, 100);

Nun, das erklärt es ein bisschen besser. Die Funktion "enthält" hat zwei Parameter:

  • $ set - repräsentiert eine Menge, die als Funktion definiert ist.
  • $ elem - repräsentiert ein Element, das als Wert definiert ist.

In diesem Zusammenhang alles "enthält" zu tun ist, die Funktion in anzuwenden $ set mit dem Parameter $ elem. Lassen Sie uns alle in einen Test einwickeln.

class FunSetsTest erweitert PHPUnit_Framework_TestCase private $ funSets; geschützte Funktion setUp () $ this-> funSets = new FunSets ();  function testContainsIsImplemented () // Wir charakterisieren eine Menge durch ihre include-Funktion. Es ist die Grundfunktion eines Sets. $ set = Funktion ($ Element) return true;; $ this-> assertTrue ($ this-> funSets-> enthält ($ set, 100)); 

Und verpacken Sie unseren Produktionscode FunSets.php in eine Klasse:

class FunSets public-Funktion enthält ($ set, $ elem) return $ set ($ elem); 

Sie können diesen Test tatsächlich ausführen und er wird bestanden. Die Menge, die wir für diesen Test definiert haben, ist nur eine Funktion, die immer true zurückgibt. Es ist eine "wahre Menge".

Das Singleton Set

Wenn das vorige Kapitel ein wenig verwirrend war oder in der Logik unbrauchbar aussah, wird es dies etwas klarer machen. Wir möchten ein Set mit einem einzelnen Element, einem Singleton-Set, definieren. Denken Sie daran, dass dies eine Funktion sein muss, und wir möchten sie wie im unten stehenden Test verwenden.

function testSingletonSetContainsSingleElement () // Eine Singleton-Gruppe, die von einer Funktion charakterisiert wird, an die übergeben wurde, gibt für das einzelne Element den Wert // zurück, der als Parameter übergeben wird. Mit anderen Worten, ein Singleton ist eine Menge mit einem einzelnen Element. $ singleton = $ this-> funSets-> singletonSet (1); $ this-> assertTrue ($ this-> funSets-> enthält ($ singleton, 1)); 

Wir müssen eine Funktion definieren, die aufgerufen wird "singeltonSet" mit einem Parameter, der ein Element der Menge darstellt. Im Test ist das die Nummer Eins (1). Dann erwarten wir unsere enthält Methode, wenn mit einer Singleton-Funktion aufgerufen, um zurückzukehren wahr wenn der gesendete Parameter gleich eins ist. Der Code, der den Test bestanden hat, lautet wie folgt:

public function singletonSet ($ elem) return-Funktion ($ otherElem) use ($ elem) return $ elem == $ otherElem; ; 

Beeindruckend! Das ist verrückt. Also die Funktion "singletonSet" bekommt als Parameter ein Element als $ elem. Dann wird eine andere Funktion zurückgegeben, die einen Parameter hat $ otherElem und diese zweite Funktion wird vergleichen $ elem zu $ otherElem.

Wie funktioniert das? Zuerst diese Zeile:

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

verwandelt sich in was "singletonSet (1)" kehrt zurück:

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

Dann "enthält ($ singleton, 1)" wird genannt. Welches wiederum ruft was in ist $ singleton. Der Code wird also:

$ singleton (1)

Mit welcher der Code tatsächlich ausgeführt wird $ otherElem den Wert eins haben.

return 1 == 1

Was natürlich stimmt und unser Test bestanden hat.

Lächelst du schon Fühlen Sie, wie Ihr Gehirn anfängt zu kochen? Ich habe es sicherlich getan, als ich dieses Beispiel zum ersten Mal in Scala geschrieben habe, und ich habe es wieder getan, als ich dieses Beispiel zum ersten Mal in PHP geschrieben habe. Ich finde das außergewöhnlich. Es ist uns gelungen, eine Menge mit einem Element zu definieren, mit der Möglichkeit zu überprüfen, dass sie den Wert enthält, den wir in sie übergeben haben. Wir haben das alles ohne eine einzige Wertzuweisung gemacht. Wir haben keine Variable, die den Wert Eins enthält oder den Status Eins hat. Kein Zustand, keine Zuordnung, keine Veränderlichkeit, keine Schleifen. Wir sind hier auf dem richtigen Weg.


Vereinigung der Sets

Da wir nun einen Satz mit einem einzigen Wert erstellen können, müssen wir einen Satz mit mehreren Werten erstellen können. Der naheliegende Weg besteht darin, die Gewerkschaftsoperation auf unseren Sets zu definieren. Die Vereinigung zweier Singleton-Sets repräsentiert eine andere Vereinigung mit beiden Werten. Ich möchte, dass Sie sich eine Minute Zeit nehmen und über die Lösung nachdenken, bevor Sie zum Code scrollen. Nehmen Sie vielleicht einen Blick auf die unten aufgeführten Tests.

function testUnionContainsAllElements () // Eine Union ist durch eine Funktion gekennzeichnet, die 2 Sets als Parameter abruft und alle bereitgestellten Sets enthält. // Wir können an dieser Stelle nur Singletons erstellen, also erstellen wir 2 Singletons und vereinigen sie mit $ s1 = $ this -> funSets-> singletonSet (1); $ s2 = $ this-> funSets-> singletonSet (2); $ union = $ this-> funSets-> union ($ s1, $ s2); // Überprüfen Sie nun, ob sowohl 1 als auch 2 Teil der Union sind. $ This-> assertTrue ($ this-> funSets-> enthält ($ union, 1)); $ this-> assertTrue ($ this-> funSets-> enthält ($ union, 2)); //… und dass es nicht 3 $ this-> assertFalse enthält ($ this-> funSets-> enthält ($ union, 3)); 

Wir wollen eine Funktion namens "Union" das bekommt zwei Parameter, beide Sätze. Denken Sie daran, Sets sind nur Funktionen für uns, also unsere "Union" Funktion erhält zwei Funktionen als Parameter. Dann wollen wir mit überprüfen können "enthält" ob die Vereinigung ein Element enthält oder nicht. So unser "Union" Funktion muss eine andere Funktion zurückgeben "enthält" Kann benutzen.

public function union ($ s1, $ s2) return-Funktion ($ otherElem) use ($ s1, $ s2) return $ this-> enthält ($ s1, $ otherElem) || $ this-> enthält ($ s2, $ otherElem); ; 

Das funktioniert eigentlich ganz gut. Und es ist vollkommen gültig, auch wenn Ihre Gewerkschaft zusammen mit einer anderen Gewerkschaft plus einem Einzelspieler aufgerufen wird. Es ruft enthält in sich für jeden Parameter. Wenn es eine Gewerkschaft ist, wird es wiederkehren. So einfach ist das.


Überschneidung und Unterschied

Wir können dieselbe Einzeilenlogik mit geringfügigen Änderungen anwenden, um die nächsten zwei wichtigsten Funktionen zu erhalten, die eine Menge charakterisieren: Schnittmenge - enthält nur die gemeinsamen Elemente zwischen zwei Mengen - und die Differenz - enthält nur die Elemente der ersten Menge, die nicht Teil sind vom zweiten Satz.

public function intersect ($ s1, $ s2) return-Funktion ($ otherElem) use ($ s1, $ s2) return $ this-> enthält ($ s1, $ otherElem) && $ this-> enthält ($ s2, $ otherElem); ;  public function diff ($ s1, $ s2) return-Funktion ($ otherElem) use ($ s1, $ s2) return $ this-> enthält ($ s1, $ otherElem) &&! $ this-> enthält ($ s2 , $ otherElem); ; 

Ich werde Sie nicht mit dem Testcode für diese beiden Methoden überfluten. Die Tests werden geschrieben und Sie können sie überprüfen, wenn Sie in dem beigefügten Code nachsehen.


Filtersatz

Nun, das ist etwas komplizierter, wir können das nicht mit einer einzigen Codezeile lösen. Ein Filter ist eine Funktion, die zwei Parameter verwendet: eine Mengen- und eine Filterfunktion. Sie wendet die Filterfunktion auf eine Menge an und gibt eine andere Menge zurück, die nur die Elemente enthält, die die Filterfunktion erfüllen. Um es besser zu verstehen, hier ist der Test dafür.

function testFilterContainsOnlyElementsThatMatchConditionFunction () $ u12 = $ this-> createUnionWithElements (1, 2); $ u123 = $ this-> funSets-> union ($ u12, $ this-> funSets-> singletonSet (3)); // Filterregel, Elemente suchen, die größer als 1 sind (Bedeutung 2 und 3). $ Condition = function ($ elem) return $ elem> 1;; // Gefiltertes Set $ ​​gefiltertSet = $ this-> funSets-> filter ($ u123, $ condition); // Vergewissern Sie sich, dass das gefilterte Set nicht 1 enthält. $ This-> assertFalse ($ this-> funSets-> enthält ($ gefiltertesSet, 1), "Sollte nicht 1 enthalten"); // Überprüfen Sie, ob es 2 und 3 enthält. $ This-> assertTrue ($ this-> funSets-> enthält ($ gefiltertesSet, 2), "Sollte 2 enthalten"); $ this-> assertTrue ($ this-> funSets-> enthält ($ gefiltertesSet, 3), "Sollte 3 enthalten");  private Funktion createUnionWithElements ($ elem1, $ elem2) $ s1 = $ this-> funSets-> singletonSet ($ elem1); $ s2 = $ this-> funSets-> singletonSet ($ elem2); $ this-> funSets-> union ($ s1, $ s2) zurückgeben; 

Wir erstellen ein Set mit drei Elementen: 1, 2, 3. Und wir fügen es in die Variable ein $ u123 So ist es in unseren Tests leicht zu erkennen. Dann definieren wir eine Funktion, die wir auf den Test anwenden möchten, und platzieren sie in dem Test $ Bedingung. Zum Schluss rufen wir Filter auf $ u123 mit einstellen $ Bedingung und legen Sie das Ergebnis in $ filtersSet. Dann führen wir Assertionen mit aus "enthält" um festzustellen, ob das Set so aussieht, wie wir es wollen. Unsere Bedingungsfunktion ist einfach. Sie gibt true zurück, wenn das Element größer als eins ist. Unser letzter Satz sollte also nur die Werte zwei und drei enthalten, und das überprüfen wir in unseren Behauptungen.

Filter für öffentliche Funktionen ($ set, $ condition) return-Funktion ($ otherElem) use ($ set, $ condition) if ($ condition ($ otherElem)) return $ this-> enthält ($ set, $ otherElem); falsch zurückgeben; ; 

Und jetzt gehts. Wir haben eine Filterung mit nur drei Zeilen Code implementiert. Wenn die Bedingung für das bereitgestellte Element zutrifft, führen wir eine Menge der Menge für das Element aus. Wenn nicht, kehren wir einfach zurück falsch. Das ist es.


Elemente überlaufen

Im nächsten Schritt erstellen Sie verschiedene Schleifenfunktionen. Der erste, "für alle()", wird ein nehmen $ set und ein $ Bedingung und zurück wahr ob $ Bedingung gilt für alle Elemente der $ set. Dies führt zu folgendem Test:

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

Wir haben das extrahiert $ u123 Erstellung aus dem vorherigen Test in eine private Methode. Dann definieren wir drei verschiedene Bedingungen: höher als null, höher als eins und höher als zwei. Da unser Set die Zahlen eins, zwei und drei enthält, sollte nur die Bedingung "höher als null" den Wert "true" zurückgeben, der Rest sollte "false" sein. In der Tat können wir den Test mit Hilfe einer anderen rekursiven Methode durchführen, die verwendet wird, um alle Elemente zu durchlaufen.

private $ bound = 1000; private Funktion für allIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) Rückgabe true; elseif ($ this-> enthält ($ set, $ currentValue)) return $ condition ($ currentValue) && $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); sonst Rückgabe von $ this-> forallIterator ($ currentValue + 1, $ set, $ condition);  public function forall ($ set, $ condition) return $ this-> forallIterator (- $ this-> gebunden, $ set, $ condition); 

Wir definieren zunächst einige Grenzen für unser Set. Die Werte müssen zwischen -1000 und +1000 liegen. Dies ist eine vernünftige Einschränkung, die wir auferlegen, um dieses Beispiel einfach genug zu halten. Die Funktion "für alle" ruft die private Methode auf "forallIterator" mit den erforderlichen Parametern, um rekursiv zu entscheiden, ob alle Elemente die Bedingung erfüllen. In dieser Funktion testen wir zuerst, ob wir uns außerhalb der Grenzen befinden. Wenn ja, kehre zurück. Prüfen Sie dann, ob unser Set den aktuellen Wert enthält, und geben Sie den aktuellen, auf die Bedingung angewendeten Wert zusammen mit einem logischen "UND" mit einem rekursiven Aufruf an uns selbst mit dem nächsten Wert zurück. Ansonsten rufen Sie uns einfach mit dem nächsten Wert an und geben Sie das Ergebnis zurück.

Das funktioniert gut, wir können es genauso implementieren wie "exists ()". Das kehrt zurück wahr wenn eines der Elemente die Bedingung erfüllt.

private Funktion existsIterator ($ currentValue, $ set, $ condition) if ($ currentValue> $ this-> bound) return false; elseif ($ this-> enthält ($ set, $ currentValue)) liefert $ condition ($ currentValue) || $ this-> existsIterator ($ currentValue + 1, $ set, $ condition); sonst Rückgabe von $ this-> existsIterator ($ currentValue + 1, $ set, $ condition);  public function exists ($ set, $ condition) return $ this-> existsIterator (- $ this-> gebunden, $ set, $ condition); 

Der einzige Unterschied ist, dass wir wiederkommen falsch Wenn außerhalb der Grenzen und wir verwenden "ODER" anstelle von "UND" im zweiten, wenn.

Jetzt, "Karte()" wird anders, einfacher und kürzer sein.

Öffentliche Funktionskarte ($ set, $ action) return-Funktion ($ currentElem) use ($ set, $ action) return $ this-> exists ($ set, Funktion ($ elem) use ($ currentElem, $ action)  return $ currentElem == $ action ($ elem);); ; 

Mapping bedeutet, dass wir eine Aktion auf alle Elemente einer Menge anwenden. Für eine Karte benötigen wir keinen Helfer-Iterator, wir können ihn wiederverwenden "exists ()" und die Elemente von "Existieren" zurückgeben, die das Ergebnis von erfüllen $ action angewendet $ element. Dies kann auf dem ersten Standort nicht offensichtlich sein. Sehen wir uns also an, was passiert.

  • Wir schicken den Satz 1, 2 und die Aktion $ element * 2 (doppelt) zur Karte.
  • Es wird natürlich eine Funktion zurückgegeben, die einen Parameter als Element hat und die Menge und die Aktion von einer höheren Ebene verwendet.
  • Diese Funktion ruft an existiert mit dem Set 1, 2 und die Bedingungsfunktion $ currentElement gleich $ elem * 2.
  • existiert () wird über alle Elemente zwischen -1000 und +1000, unsere Grenzen, iterieren. Wenn es ein Element findet, ist das Doppelte dessen, was kommt "enthält" (der Wert von $ currentElement) es wird zurückkehren wahr.
  • Mit anderen Worten, der letzte Vergleich wird zurückgegeben wahr für den Aufruf von enthält mit Wert zwei, wenn der mit zwei multiplizierte Wert des Stroms zwei ergibt. Für das erste Element des Sets, eins, wird es bei zwei true zurückgegeben. Für das zweite Element zwei auf Wert vier.

Ein praktisches Beispiel

Nun, funktionale Programmierung macht Spaß, ist aber bei PHP alles andere als ideal. Daher empfehle ich Ihnen nicht, ganze Anwendungen auf diese Weise zu schreiben. Nachdem Sie nun gelernt haben, was PHP funktional tun kann, können Sie Teile dieses Wissens in Ihren täglichen Projekten anwenden. Hier ist ein Beispiel für ein Authentifizierungsmodul. Das AuthPlugin class stellt eine Methode bereit, die einen Benutzer und ein Kennwort empfängt und den Benutzer magisch authentifiziert und seine Berechtigungen setzt.

class AuthPlugin private $ permissions = array (); Funktion authenticate ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ adminModules = neue AdminModules (); $ this-> permissions [] = $ adminModules-> allowRead ($ username); $ this-> permissions [] = $ adminModules-> allowWrite ($ username); $ this-> permissions [] = $ adminModules-> allowExecute ($ username);  private Funktion verifyUser ($ Benutzername, $ Kennwort) //… BENUTZER- / PASS-ÜBERPRÜFUNG //… LOAD USER DETAILS, ETC. 

Das hört sich vielleicht OK an, hat aber ein riesiges Problem. 80% der "authentifizieren()" Methode verwendet Informationen aus der "AdminModule". Dies schafft eine sehr starke Abhängigkeit.


Es wäre viel sinnvoller, die drei Aufrufe anzunehmen und eine einzige Methode zu erstellen AdminModule.


Also, indem wir die Generation in bewegen AdminModule Es ist uns gelungen, drei Abhängigkeiten auf nur eine zu reduzieren. Die öffentliche Schnittstelle von AdminModule wurde auch von drei auf nur eine Methode reduziert. Wir sind jedoch noch nicht da. AuthPlugin noch direkt abhängig von AdminModule.

Ein objektorientierter Ansatz

Wenn unser Authentifizierungs-Plugin von einem beliebigen Modul verwendet werden soll, müssen wir eine gemeinsame Schnittstelle für diese Module definieren. Lassen Sie uns die Abhängigkeit einführen und eine Schnittstelle einführen.

class AuthPlugin private $ permissions = array (); privates $ appModule; Funktion __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  Funktion authenticate ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = array_merge ($ this-> permissions, $ this-> appModule-> getPermissions ($ username));  private Funktion verifyUser ($ Benutzername, $ Kennwort) //… BENUTZER- / PASS-ÜBERPRÜFUNG //… LOAD USER DETAILS, ETC. 

AuthPlugin habe einen Konstruktor bekommen. Es erhält einen Parameter vom Typ Anwendungsmodul, eine Schnittstelle und Anrufe "getPermissions ()" auf diesem injizierten Objekt.

interface ApplicationModule öffentliche Funktion getPermissions ($ username); 

Anwendungsmodul Definiert eine einzelne öffentliche Methode, "getPermissions ()", mit einem Benutzernamen als Parameter.

Klasse AdminModules implementiert ApplicationModule // […]

Endlich, AdminModule muss das implementieren Anwendungsmodul Schnittstelle.


Das ist jetzt viel besser. Unsere AuthPlugin hängt nur von einer Schnittstelle ab. AdminModule hängt von der gleichen Schnittstelle ab, also die AuthPlugin wurde Modulagnostiker. Wir können eine beliebige Anzahl von Modulen erstellen, die alle implementieren Anwendungsmodul, und AuthPlugin kann mit allen zusammenarbeiten.

Ein funktionaler Ansatz

Eine andere Möglichkeit, die Abhängigkeit umzukehren und zu machen AdminModule, oder ein beliebiges anderes Modul, um die AuthPlugin ist, in diese Module eine Abhängigkeit zu injizieren AuthPlugin. AuthPlugin bietet eine Möglichkeit zum Festlegen der Authentifizierungsfunktion, und jede Anwendung sendet eine eigene "Erlaubnis bekommen()" Funktion.

class AdminModules privates $ authPlugin; Funktion __construct (Authentitcation $ authPlugin) $ this-> authPlugin = $ authPlugin;  private Funktion allowRead ($ username) return "yes";  private Funktion allowWrite ($ username) return "no";  private Funktion allowExecute ($ username) return $ username == "joe"? "ja Nein";  private Funktion authenticate () $ this-> authPlugin-> setPermissions (Funktion ($ username) $ permissions = array (); $ permissions [] = $ this-> allowRead ($ username); $ permissions [] = $) this-> allowWrite ($ username); $ permissions [] = $ this-> allowExecute ($ username); $ permissions zurückgeben;); $ this-> authPlugin-> authenticate (); 

Wir fangen mit an AdminModule. Es implementiert nichts mehr. Es verwendet jedoch ein injiziertes Objekt, das die Authentifizierung implementieren muss. Im AdminModule es wird eine geben "authentifizieren()" Methode, die aufgerufen wird "Berechtigungen festlegen()" auf AuthPlugin und übergeben Sie die Funktion, die verwendet werden muss.

interface Authentication function setPermissions ($ permissionGrantingFunction); Funktion authenticate (); 

Die Authentifizierungsschnittstelle definiert einfach die beiden Methoden.

class AuthPlugin implementiert Authentication private $ permissions = array (); privates $ appModule; private $ permissionsFunction; Funktion __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule;  Funktion authenticate ($ username, $ password) $ this-> verifyUser ($ username, $ password); $ this-> permissions = $ this-> permissionsFunction ($ username);  private Funktion verifyUser ($ Benutzername, $ Kennwort) //… BENUTZER- / PASS-ÜBERPRÜFUNG //… LOAD USER DETAILS, ETC.  public function setPermissions ($ permissionGrantingFunction) $ this-> permissionsFunction = $ permissionGrantingFunction; 

Endlich, AuthPlugin implementiert die Authentifizierung und legt die eingehende Funktion in einem privaten Klassenattribut fest. Dann, "Authentifizierung()" wird eine dumme Methode. Es ruft einfach die Funktion auf und setzt dann den Rückgabewert. Es ist völlig entkoppelt von dem, was kommt.


Wenn wir das Schema betrachten, gibt es zwei wichtige Änderungen:

  • Anstatt AdminModule, AuthPlugin implementiert die Schnittstelle.
  • AuthPlugin wird "Rückruf" AdminModule, oder was auch immer ein anderes Modul in der Berechtigungsfunktion gesendet hat.

Welches zu benutzen?

Auf diese Frage gibt es keine richtige Antwort. Ich würde behaupten, dass der objektorientierte Ansatz angemessener ist, wenn der Prozess zur Bestimmung der Berechtigungen ziemlich vom Anwendungsmodul abhängt. Wenn Sie jedoch der Meinung sind, dass jedes Anwendungsmodul in der Lage sein sollte, eine Authentifizierungsfunktion bereitzustellen, und Ihre AuthPlugin ist nur ein Grundgerüst, das die Authentifizierungsfunktionalität bereitstellt, aber ohne etwas über Benutzer und Verfahren zu wissen, können Sie sich für den funktionalen Ansatz entscheiden.

Der funktionale Ansatz macht Ihre AuthPlugin sehr abstrakt und darauf kann man sich verlassen. Wenn Sie jedoch planen, Ihre AuthPlugin Wenn Sie mehr tun und mehr über Benutzer und Ihr System wissen möchten, wird es zu konkret, und Sie möchten sich nicht darauf verlassen. Wählen Sie in diesem Fall den objektorientierten Weg und lassen Sie den Beton AuthPlugin hängen von den abstrakteren Anwendungsmodulen ab.