Der php-Generator – seine Fähigkeit
Diese Datei ist der ENTWURF Ihres ausgekoppelten php-Generators (generators/php/):
Um den Generator zu ändern, bearbeiten Sie zuerst diese Fähigkeit, dann lassen Sie den Code damit übereinstimmen – ein Diff
an generators/php/ ohne einen deckenden Satz hier ist unvollständig.
Was er ausgibt
Eine eigenständige <stem>.php: promoted-constructor-Modellklassen, ein Client mit einer
typisierten Methode pro Operation und die eingebettete Laufzeit. PHP ≥ 8.1, HTTP über die curl-
Erweiterung – null Composer-Abhängigkeiten. Der Namespace leitet sich vom API-Titel ab
(identifierFor(title, pascal) – z.B. CafeOrders).
Designentscheidungen, die bestehen müssen
- Modelle sind
final classes mit Constructor Property Promotion, erforderliche Parameter zuerst, optionale nullable= null. Die Hydrierung ist kompilierungszeit-generiert pro Klasse:fromArray(array $data): selfundtoArray(): array(Wire-Namen inline; Nulls werden bei der Serialisierung übersprungen) – keine Reflection.omit-Schemas hydrieren/serialisieren über ihre Basisklasse. Eine Eigenschaft oder Antwort, die als DISKRIMINIERTE Union typisiert ist, hydriert über denunmarshalX-Dispatcher der Union, sodass Verbraucher mitinstanceofeingrenzen können; ihr Typ enthältarray, weil ein Wert mit einem unbekannten Diskriminator das rohe Array bleibt. Jede andere Union bleibt das dekodierte JSON, also ist ihr Typ der des JSONs: ein Klassenmitglied istarray, ein Enum-Mitglied ist sein zugrunde liegender Skalar, und ein Datumsmitglied iststring. - Die Datei besteht PHPStan Level 5. Sie landet im eigenen Projekt des Benutzers, also liest
sein Analysator sie. Die Operationstabelle ist eine Konstante, die PHPStan genau kennt, daher liest eine Methode
$op['security']nur, wenn die Tabelle diesen Eintrag hat, und übergibt sonst[]. - Eine
writeOnly-Eigenschaft ist optional selbst wenn erforderlich: Requests und Responses teilen sich eine Klasse, und eine Response trägt sie nie. Sie ist nullable= null, sodassfromArrayeine Response ohne sie hydriert undtoArraysie in einem Request trotzdem sendet. - Die
Client-Klasse ist NICHTfinal– PHP-Testsuiten mocken konkrete Klassen (createMock(Client::class)), undfinalwürde eine Wrapper-Schnittstelle für jeden Verbraucher erzwingen. Modellklassen bleibenfinal. - Jeder Parameter ist sein eigenes Argument, daher teilen sich ihre Namen einen Namensraum mit den
Argumenten, die die Methode selbst deklariert (
$body,$headers,$idempotencyKey). Bauen Sie sie mituniqueIdentifiers(..., { taken: … }): OpenAPI erlaubt einer Operation, einen Namen an zwei Stellen zu verwenden (idim Pfad UND in der Query), und PHP lehnt einen neu definierten Parameter direkt ab. Der Wire-Name bleibt unberührt, daher bleibt der Request unverändert. - Die Operations-Map ist eine Klassenkonstante,
Client::OPERATIONS, gelesen überself::OPERATIONS[...], niemals einconstauf Dateiebene. OPcache Preload behält Klassen und Funktionen im gemeinsamen Speicher, verwirft aber Konstanten auf Dateiebene, und ein Autoloader inkludiert die Datei nie wieder, sobaldClientexistiert – daher ist eine Map auf Dateiebene bei jedem Request nach dem ersten undefiniert. Eine Klassenkonstante wird mit der Klasse gespeichert. - Benennung: Klassen PascalCase, Eigenschaften/Methoden camelCase via
identifierFor(..., RESERVED_WORDS.php); reservierte Wörter erhalten einen nachgestellten Unterstrich. - Enums sind native Backed Enums (string/int); andere Skalare bleiben Aliase.
Diskriminierte Unions sind
match-basierteunmarshalX(array $data)-Dispatcher. Jeder Arm hydriert sein Mapping-Ziel so, wie eine Eigenschaft dieses Typs es tut, sodass ein Ziel ohne eigene Klasse (ein einfachesoneOf) ein rohes Array bleibt; allOf wird flachgeklappt. - Unions behalten ihre Typen, wo PHP 8.1 sie ausdrücken kann. Eine Union aus Skalaren, Enums,
Klassen oder Arrays wird zu einem nativen Union-Typ (
int|string,string|array) anstatt aufmixedzu kollabieren – reichhaltige Listenfilter sind der häufige Fall und ihre Typen zu verlieren, macht den Sinn eines typisierten SDKs zunichte. Sie fällt nur aufmixedzurück, wenn ein Member keinen eigenen PHP-Typ hat (ein Inline-Objekt, eine Intersection,unknown), weilmixednicht innerhalb einer Union auftreten kann. Nullability wird als|nullin einer Union ausgedrückt (PHP verbietet die Mischung von?mit|) und?Tfür einen einzelnen Typ. - Fehler: Exceptions SIND der Fehlermodus (
ApiError/TimeoutErrorerweitern\RuntimeException);errorModeändert die Ausgabe nicht (der Generator deklarierterrorModes: ['throw'], also schlägtresultschnell fehl). - Daten:
dateType: Datetypisiertformat: date/date-timeals\DateTimeImmutable; Hydrierung istnew \DateTimeImmutable(...)und Serialisierung formatiert mit\DateTimeInterface::ATOM(date-time) oder'Y-m-d'(date), einschließlich für Query-Parameter. - Methodenargumente: Erforderliche Pfadparameter positionell, JSON-Body als nächstes, optionale Query-
Parameter als nullable BENANNTE Argumente, dann
?array $headersund?string $idempotencyKeybei mutierenden Methoden. - Nicht-JSON-Erfolgsbodies (PDFs, Bilder, Oktett-Streams) geben den rohen Body als
stringzurück – ein binärer Download darf niemals aufvoidzurückfallen. - PHPDoc trägt, was die Signatur nicht kann. PHP's
arrayund\Generatorlöschen ihren Elementtyp, daher gibt ein Docblock ihn an:@return Customer[]für Collection-Rückgaben und@return \Generator<int, Customer>bei<op>Pages()/<op>Items(). Statische Analyse und Leser gehen danach; eine hydrierte Rückgabe ohne Annotation wirkt untypisiert. - Response-Header: Eine Operation, die Erfolgs-Response-Header DEKLARIERT, erhält eine
<op>WithHeaders()-Variante, die einEnvelopezurückgibt (data,headers– konvertiert zu int/bool/string mit camelCase-Keys, fehlende/nicht parshare Werte weggelassen – undstatus). Operationen ohne deklarierte Header erhalten keine Variante, und die Basismethode bleibt body-only (PHP kann den Rückgabetyp nicht auf ein Flag variieren). - Server: Wenn die Beschreibung Server deklariert, wird eine
Servers-Klasse mit einer statischen Methode pro Server ausgegeben; Server-VARIABLEN werden zu benannten String-Argumenten, die auf die Standardwerte der Spezifikation voreingestellt sind (Servers::production(organizationId: 'org_x')), sodass templatisierte Basis-URLs keine manuelle String-Konstruktion benötigen. Der eingebackene Standard des Clients bleibtservers[0]mit substituierten Variablen-Standardwerten. - Stream-Bodies werden durchgereicht. Bei einer untypisierten
multipart/form-data-Operation (isUntypedMultipartBodyaus dem Toolkit: ein Schema, das weder ein Objekt noch ein$refist) oder einer binären Operation (isBinaryContentType), ist der Bodymixedund die Laufzeit klärt es (multipartBody/binaryBody): Eine Stream-resourcewird von curl in Chunks hochgeladen (CURLOPT_UPLOAD+CURLOPT_INFILE, die Methode beibehalten vonCURLOPT_CUSTOMREQUEST), niemals in einen String gelesen, in genau einem Versuch, unabhängig von der Wiederholungsrichtlinie, weil ein verbrauchter Stream nicht wiederholt werden kann.CURLOPT_INFILESIZEwird nur gesetzt, wenn der Stream seekbar IST UNDfstatein Array zurückgibt (die Bytes ab der aktuellen Position);php://input, Pipes und Wrapper ohnestream_statgehen chunked. Einstring-Body einer dieser Operationen wird roh gesendet, nichtjson_encoded; jeder andere binäre Operationswert wird weiterhinjson_encoded; einarray-Body einer untypisierten Multipart-Operation behält dentoMultipart-Pfad. DerContent-Typedes Aufrufers gewinnt über den der Beschreibung oder der Multipart-Grenze, case-insensitiv insendgematcht, sodass der Header nie zweimal gesendet wird; diese Regel gilt für jede Operation. Das ganze-Request-curl-Time-out gilt weiterhin (timeout: nulldeaktiviert es). Typisierte Multipart-Bodies sind nicht betroffen. - Parity-Oberfläche: Auth, Wiederholungen mit
Retry-After+ Jittered Backoff, Per-Attempt- curl-Time-outs, Middleware-Callables, Paginierung (<op>Pages()/<op>Items()als\Generators), SSE (iterSseüber eine curl_multi-Pumpe), Multipart. - Die Laufzeit ist handgeschrieben in
runtime/runtime.phpin diesem Ordner (php -l-sauber) und wird zur Vorbereitungszeit eingebettet.curl_closewird nie aufgerufen (seit PHP 8.5 deprecated, seit 8.0 ein No-Op). Unter--runtime modulewird sie als eineruntime.phpgeschrieben, die der Clientrequire_oncet, mit ihrem Namespace auf den des Clients umgeschrieben, sodass ein Namespace beide Dateien umspannt. onSettledsieht jeden gesendeten Aufruf genau einmal.send()ruft jedenConfig->onSettled-Callable, dann jedenConfig::$registeredOnSettled-Callable auf, nach der Wiederholungsschleife mitoperationId,status(0 ohne Response),timedOut,attemptsunddurationMs, bevor es wirft oder zurückkehrt. Jeder Hook läuft. Der erste Hook- Fehler wird bei einem erfolgreichen Aufruf geworfen; bei einem fehlgeschlagenen Aufruf gewinnt der ursprüngliche Fehler und der Hook-Fehler geht anerror_log. SSE-Streams öffnen außerhalb vonsend(), daher erreichen sie keinen Hook.- Telemetrie ist zur Generierungszeit opt-in. Mit
client.telemetry: onschreibt der Generator auch<client>.telemetry.php(vontelemetry/telemetry.phpin diesem Ordner, zur Vorbereitungszeit eingebettet) neben den Client, in beiden Laufzeitmodi, mit dem Namespace des Clients und der Generatorversion eingebacken. Die Client-Datei ändert sich nicht. Die Anwendung requiret sie und übergibtnew UsageTelemetry(...)inConfig->onSettled. Ein SDK-Besitzer ruftregister()stattdessen auf, was die Instanz zuConfig::$registeredOnSettledhinzufügt, sodass jeder Client seine Aufrufe zählt;UsageTelemetry::flushRegistered()leert jede registrierte Instanz. PHP hat keine Timer: Ein Aufruf, der sein Zeitfenster abgelaufen findet, sendet es zuerst, und eine Shutdown-Funktion sendet das offene Fenster. Konstanten leben auf der Klasse, weil OPcache Preloading Konstanten auf Dateiebene verwirft. Der Klassenname ist für jedes SDK reserviert, weil die Datei den Namespace mit den Modellen teilt. - Nur mit dem neutralen Toolkit erstellt – der Dogfooding-Guard schlägt sonst fehl.
Migration von einem servicebasierten SDK
-
Pro-Ressource-Services (
$client->customers()->get($id)) werden auf flache Methoden abgebildet, die nach OperationIds benannt sind ($client->getCustomer($id)); optionale Query-Parameter behalten ihren benannten-Argument-Stil (filter:,sort:,limit:). -
Collection-Wrapper, die Paginierungs-RESPONSE-HEADER bereitstellen (
getTotalItems(),getLimit()), werden auf das<op>WithHeaders()-Envelope abgebildet (->headers['paginationTotal']); einfache Iteration wird auf<op>Items()/<op>Pages()-Generators abgebildet. -
Dedizierte Validierungs-Exception-Klassen, die Feld-Fehler bereitstellen, werden auf
catch (ApiError $e)+$e->status === 422+ den dekodierten$e->bodyabgebildet. -
Session-/Bearer-Token-Flows werden auf
auth: ['bearer' => $tokenProvider]mit einem Callable abgebildet – pro Request aufgelöst, sodass Refresh keinen Client-Neubau benötigt. -
Es dokumentiert sich selbst. Mit
client.docs(oder--docs) schreibt derdocs-Hook<stem>.php.md: die Sicherheitsschemata, dann einen Abschnitt pro Operation mit ihren Parametern, Body, Response-Typ und Verhaltenshinweisen. Die Code-Snippets stammen aus dem eigenensample- Hook dieses Generators, sodass die Seite nur die Syntax des SDKs daneben zeigen kann, und das Layout stammt vonrenderReferencePageim Authoring-Toolkit – erreichbar von einer ausgekoppelten Kopie über@redocly/client-generator. Die Paginierung auf der Seite wird durchpaginationRuleForentschieden, denselben Helfer, mit dem dieser Generator die Paginierung auflöst.
Der Änderungs-Workflow
- Bearbeiten Sie diese Fähigkeit: Beschreiben Sie das neue Verhalten oder die neue Entscheidung.
- Lassen Sie
generators/php/damit übereinstimmen. - Führen Sie
redocly generate-clientaus und prüfen Sie dengit diffder generierten Ausgabe – generierte Dateien werden niemals von Hand bearbeitet.
Neuere eingebaute Versionen werden mit redocly eject-generator php --update eingespielt.