PHP-Generika heute (na ja, fast)

Wenn Sie PHP-Entwickler fragen, welche Möglichkeiten sie in PHP sehen möchten, nennen die meisten Generika.


Generische Unterstützung auf Sprachebene wäre die beste Lösung. Aber sie zu realisieren ist schwierig . Wir hoffen, dass eines Tages die Unterstützung der Muttersprachler Teil der Sprache wird, aber das Warten wird wahrscheinlich mehrere Jahre dauern.


Dieser Artikel zeigt, wie wir mit vorhandenen Tools, in einigen Fällen mit minimalen Änderungen, die Leistungsfähigkeit von Generika in PHP sofort nutzen können.


Von einem Übersetzer: Ich verwende bewusst Transparentpapier aus den englischen "Generika", weil Ich habe in der Kommunikation noch nie gehört, dass jemand es "generalisierte Programmierung" nannte.

Inhalt:



Was sind Generika?


Dieser Abschnitt enthält eine kurze Einführung in Generika .


Links lesen:


  • RFC zum Hinzufügen von PHP-Generika
  • Generische Unterstützung in Phan
  • Psalm Generika und Vorlagen

Einfachstes Beispiel


Da es derzeit nicht möglich ist, Generika auf Sprachebene zu definieren, müssen wir eine weitere großartige Gelegenheit nutzen - sie in den Dockblöcken zu definieren.


Wir verwenden diese Option bereits in vielen Projekten. Schauen Sie sich dieses Beispiel an:


/** * @param string[] $names * @return User[] */ function createUsers(iterable $names): array { ... } 

Im obigen Code tun wir, was auf Sprachebene möglich ist. Wir haben den Parameter $names als etwas definiert, das aufgelistet werden könnte. Wir haben auch angegeben, dass die Funktion ein Array zurückgibt. PHP TypeError einen TypeError wenn die Parametertypen und der Rückgabewert nicht übereinstimmen.


Docblock verbessert das Verständnis des Codes. $names müssen Zeichenfolgen sein und die Funktion muss ein Array von User . PHP selbst führt solche Überprüfungen nicht durch. IDEs wie PhpStorm verstehen diese Notation und warnen den Entwickler, dass der zusätzliche Vertrag nicht eingehalten wurde. Darüber hinaus können statische Analysewerkzeuge wie Psalm, PHPStan und Phan die Richtigkeit der von und zu der Funktion übertragenen Daten überprüfen.


Generika zum Bestimmen von Schlüsseln und Werten von Aufzählungstypen


Oben ist das einfachste Beispiel eines Generikums. Komplexere Methoden umfassen die Möglichkeit, den Typ der Schlüssel sowie den Wertetyp anzugeben. Nachfolgend finden Sie eine Möglichkeit, dies zu beschreiben:


 /** * @return array<string, User> */ function getUsers(): array { ... } 

Hier heißt es, dass das von getUsers Array Zeichenfolgenschlüssel und Werte vom Typ User .


Statische Analysegeräte wie Psalm, PHPStan und Phan verstehen diese Anmerkung und berücksichtigen sie bei der Überprüfung.


Betrachten Sie den folgenden Code:


 /** * @return array<string, User> */ function getUsers(): array { ... } function showAge(int $age): void { ... } foreach(getUsers() as $name => $user) { showAge($name); } 

Statische Analysatoren geben showAge Aufruf Argument 1 of showAge expects int, string provided eine Warnung mit folgendem Fehler aus: Argument 1 of showAge expects int, string provided .


Leider weiß PhpStorm zum Zeitpunkt des Schreibens nicht, wie.


Anspruchsvollere Generika


Wir beschäftigen uns weiterhin mit dem Thema Generika. Stellen Sie sich ein Objekt vor, das ein Stapel ist :


 class Stack { public function push($item): void { ... } public function pop() { ... } } 

Der Stapel kann jeden Objekttyp akzeptieren. Was aber, wenn wir den Stapel nur auf Objekte vom Typ User beschränken möchten?


Psalm und Phan unterstützen die folgenden Anmerkungen:


 /** * @template T */ class Stack { /** * @param T $item */ public function push($item): void; /** * @return T */ public function pop(); } 

Der docblock wird verwendet, um zusätzliche Typinformationen zu übermitteln, zum Beispiel:


 /** @var Stack<User> $userStack */ $stack = new Stack(); Means that $userStack must only contain Users. 

Psalm bei der Analyse des folgenden Codes:


 $userStack->push(new User()); $userStack->push("hello"); 

Beschwert sich über Zeile 2 mit dem Fehler Argument 1 of Stack::push expects User, string(hello) provided.


PhpStorm unterstützt diese Anmerkung derzeit nicht.


Tatsächlich haben wir nur einen Teil der Informationen über Generika behandelt, aber im Moment ist dies genug.


So implementieren Sie Generika ohne Sprachunterstützung


Sie müssen die folgenden Schritte ausführen:


  • Definieren Sie auf Community-Ebene generische Standards in Dockblöcken (z. B. neue PSR oder kehren Sie zu PSR-5 zurück).
  • Fügen Sie Ihrem Code Dockblock-Anmerkungen hinzu
  • Verwenden Sie IDEs, die diese Konventionen verstehen, um statische Echtzeitanalysen durchzuführen und Inkonsistenzen zu ermitteln.
  • Verwenden Sie statische Analysewerkzeuge (z. B. Psalm) als einen der CI-Schritte, um Fehler zu erkennen.
  • Definieren Sie eine Methode zum Übergeben von Typinformationen an Bibliotheken von Drittanbietern.

Standardisierung


Im Moment hat die PHP-Community dieses generische Format inoffiziell übernommen (sie werden von den meisten Tools unterstützt und ihre Bedeutung ist den meisten klar):


 /** * @return User[] */ function getUsers(): array { ... } 

Wir haben jedoch Probleme mit einfachen Beispielen wie diesem:


 /** * @return array<string, User> */ function getUsers(): array { ... } 

Psalm versteht es und weiß, welchen Typ der Schlüssel hat und welche Werte das zurückgegebene Array hat.


Zum Zeitpunkt des Schreibens versteht PhpStorm dies nicht. Mit diesem Eintrag vermisse ich die Leistungsfähigkeit der statischen Echtzeitanalyse von PhpStorm.


Betrachten Sie den folgenden Code. PhpStorm versteht nicht, dass $user vom Typ User und $name vom Typ string ist:


 foreach(getUsers() as $name => $user) { ... } 

Wenn ich Psalm als statisches Analysewerkzeug wählen würde, könnte ich Folgendes schreiben:


 /** * @return User[] * @psalm-return array<string, User> */ function getUsers(): array { ... } 

Psalm versteht das alles.


PhpStorm weiß, dass die Variable $user vom Typ User . Er versteht jedoch immer noch nicht, dass sich der Array-Schlüssel auf eine Zeichenfolge bezieht. Phan und PHPStan verstehen die spezifischen Psalmanmerkungen nicht. Das Maximum, das sie in diesem Code verstehen, ist das gleiche wie in PhpStorm: der Typ von $user


Sie könnten argumentieren, dass PhpStorm nur das Übereinstimmungsarray array<keyType, valueType> akzeptieren sollte. Ich stimme dir nicht zu, weil Ich glaube, dass dieses Diktat von Standards die Aufgabe der Sprache und der Gemeinschaft ist und die Werkzeuge nur diesen folgen sollten.


Ich gehe davon aus, dass die oben beschriebene Vereinbarung von den meisten PHP-Mitgliedern sehr positiv aufgenommen wird. Eine, die sich für Generika interessiert. Bei Mustern wird es jedoch viel komplizierter. Weder PHPStan noch PhpStorm unterstützen derzeit Vorlagen. Im Gegensatz zu Psalm und Phan. Ihr Zweck ist ähnlich, aber wenn Sie tiefer graben, werden Sie feststellen, dass die Implementierungen etwas anders sind.


Jede der vorgestellten Optionen ist eine Art Kompromiss.


Einfach ausgedrückt, es besteht Bedarf an einer Einigung über das generische Datensatzformat:


  • Sie verbessern das Leben von Entwicklern. Entwickler können ihrem Code Generika hinzufügen und davon profitieren.
  • Entwickler können die Tools verwenden, die ihnen am besten gefallen, und bei Bedarf zwischen ihnen (Tools) wechseln.
  • Werkzeugentwickler können genau diese Werkzeuge erstellen, die Vorteile für die Community verstehen und nicht befürchten, dass sich etwas ändert oder dass ihnen ein "falscher Ansatz" vorgeworfen wird.

Werkzeugunterstützung


Psalm verfügt über alle notwendigen Funktionen zur Überprüfung von Generika. Phan auch so.


Ich bin sicher, dass PhpStorm Generika einführen wird, sobald die Community eine Vereinbarung über ein einziges Format vorlegt.


Code-Unterstützung von Drittanbietern


Der letzte Teil des allgemeinen Puzzles besteht darin, Unterstützung für Bibliotheken von Drittanbietern hinzuzufügen.


Hoffentlich werden die meisten Bibliotheken den generischen Definitionsstandard implementieren, sobald er erscheint. Dies wird jedoch nicht sofort geschehen. Einige Bibliotheken werden verwendet, haben jedoch keine aktive Unterstützung. Bei der Verwendung statischer Analysatoren zur Validierung von Typen in Generika ist es wichtig, dass alle Funktionen definiert sind, die diese Generika akzeptieren oder zurückgeben.


Was passiert, wenn Ihr Projekt auf Bibliotheken von Drittanbietern basiert, die keine generische Unterstützung bieten?


Glücklicherweise wurde dieses Problem bereits gelöst und die Stub-Funktionen sind die Lösung. Psalm, Phan und PhpStorm unterstützen Stubs.


Stubs sind normale Dateien, die Signaturen von Funktionen und Methoden enthalten, diese jedoch nicht implementieren. Durch Hinzufügen von Dockblöcken zu Stubs erhalten statische Analysetools die zusätzlichen Informationen, die sie benötigen. Zum Beispiel, wenn Sie eine Stapelklasse ohne Typhinweise und Generika wie diese haben.


 class Stack { public function push($item) { /* some implementation */ } public function pop() { /* some implementation */ } } 

Sie können eine Stub-Datei mit identischen Methoden erstellen, jedoch mit zusätzlichen Dockblöcken und ohne Implementierung von Funktionen.


 /** * @template T */ class Stack { /** * @param T $item * @return void */ public function push($item); /** * @return T */ public function pop(); } 

Wenn der statische Analysator die Stapelklasse sieht, nimmt er Typinformationen vom Stub und nicht vom tatsächlichen Code.


Die Möglichkeit, den Code von Stubs einfach zu teilen (z. B. über Composer), wäre äußerst nützlich, weil würde es ermöglichen, die geleistete Arbeit zu teilen.


Weitere Schritte


Die Gemeinde muss sich von Vereinbarungen entfernen und Standards setzen.


Vielleicht wäre die beste Option eine generische PSR?


Oder vielleicht könnten die Entwickler der wichtigsten statischen Analysegeräte, PhpStorm, andere IDEs und alle an der Entwicklung von PHP (zur Steuerung) beteiligten Personen einen Standard entwickeln, den jeder verwenden würde.


Sobald der Standard angezeigt wird, kann jeder beim Hinzufügen von Generika zu vorhandenen Bibliotheken und Projekten helfen und Pull-Anforderungen erstellen. Und wo dies nicht möglich ist, können Entwickler Stubs schreiben und teilen.


Wenn alles erledigt ist, können wir Tools wie PhpStorm verwenden, um die Generika in Echtzeit zu überprüfen, während wir den Code schreiben. Wir können statische Analysetools als Teil unseres CI als Sicherheitsgarantie verwenden.


Generika können auch in PHP implementiert werden (na ja, fast).


Einschränkungen


Es gibt eine Reihe von Einschränkungen. PHP ist eine dynamische Sprache, mit der Sie viele "magische" Dinge wie diese tun können. Wenn Sie zu viel PHP-Magie verwenden, kann es vorkommen, dass statische Analysatoren nicht alle Typen im System genau extrahieren können. Wenn irgendwelche Typen unbekannt sind, können die Tools Generika nicht in allen Fällen korrekt verwenden.


Die Hauptanwendung dieser Analyse ist jedoch die Validierung Ihrer Geschäftslogik. Wenn Sie sauberen Code schreiben, sollten Sie nicht zu viel Magie verwenden.


Warum fügen Sie der Zunge nicht einfach Generika hinzu?


Das wäre die beste Option. PHP hat Open Source Code und niemand stört Sie, die Quellen zu klonen und Generika zu implementieren!


Was ist, wenn ich keine Generika benötige?


Ignorieren Sie einfach alle oben genannten Punkte. Einer der Hauptvorteile von PHP besteht darin, dass es flexibel bei der Auswahl der geeigneten Komplexität der Implementierung ist, je nachdem, was Sie erstellen. Mit einem einmaligen Code müssen Sie nicht über Dinge wie das Tippen nachdenken. Bei großen Projekten lohnt es sich jedoch, solche Möglichkeiten zu nutzen.


Vielen Dank an alle, die diesen Ort gelesen haben. Ich freue mich über Ihre Kommentare in der PM.

UPD : ghost404 in den Kommentaren stellte fest, dass PHPStan ab Version 0.12.x Psalmanmerkungen versteht und Generika unterstützt

Source: https://habr.com/ru/post/de456466/


All Articles