CommunityCodierung & Entwicklunggithub.com

Redocly/php-generator

Entwurf des ausgekoppelten Redocly `php`-Client-Generators. Lesen und aktualisieren Sie ihn, bevor Sie `generators/php/` ändern.

Was ist php-generator?

Diese Fähigkeit beschreibt den vollständigen Entwurf des ausgekoppelten PHP-Client-Generators von Redocly (`generators/php/`). Jede Änderung am Generator muss zuerst in dieser SKILL.md dokumentiert werden, bevor der eigentliche Code geändert wird – ein Diff ohne zugehörigen Satz in dieser Datei gilt als unvollständig. Der Generator erzeugt eine einzige, eigenständige PHP-Datei mit promovierten Konstruktor-Modellklassen (PHP ≥ 8.1), einem `Client` mit einer typisierten Methode pro Operation und einer eingebetteten Laufzeit ohne Composer-Abhängigkeiten. Der Generator unterstützt die Plattformen *Redocly CLI* und *Redocly API*-Workflows und legt großen Wert auf Designentscheidungen wie `final class`-Modelle, PHPStan Level 5-Kompatibilität, korrekte Behandlung von `writeOnly`-Eigenschaften, optimierte OPcache-Unterstützung durch Klassenkonstanten für Operations-Maps, native PHP-Enums, `match`-basierte diskriminierte Unions, Streaming von Binärdaten und Multipart-Requests sowie optionale Telemetrie. Der Generator bietet zudem einen umfassenden Migrationspfad von servicebasierten SDKs, eine Selbst-Dokumentationsfunktion und einen klaren Änderungs-Workflow: Skill bearbeiten → Code anpassen → Client generieren und Diff prüfen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Redocly/redocly-cli/tree/HEAD/packages/client-generator/eject-assets/skills/php-generator

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

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): self und toArray(): 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 den unmarshalX-Dispatcher der Union, sodass Verbraucher mit instanceof eingrenzen können; ihr Typ enthält array, 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 ist array, ein Enum-Mitglied ist sein zugrunde liegender Skalar, und ein Datumsmitglied ist string.
  • 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, sodass fromArray eine Response ohne sie hydriert und toArray sie in einem Request trotzdem sendet.
  • Die Client-Klasse ist NICHT final – PHP-Testsuiten mocken konkrete Klassen (createMock(Client::class)), und final würde eine Wrapper-Schnittstelle für jeden Verbraucher erzwingen. Modellklassen bleiben final.
  • 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 mit uniqueIdentifiers(..., { taken: … }): OpenAPI erlaubt einer Operation, einen Namen an zwei Stellen zu verwenden (id im 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 über self::OPERATIONS[...], niemals ein const auf Dateiebene. OPcache Preload behält Klassen und Funktionen im gemeinsamen Speicher, verwirft aber Konstanten auf Dateiebene, und ein Autoloader inkludiert die Datei nie wieder, sobald Client existiert – 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-basierte unmarshalX(array $data)-Dispatcher. Jeder Arm hydriert sein Mapping-Ziel so, wie eine Eigenschaft dieses Typs es tut, sodass ein Ziel ohne eigene Klasse (ein einfaches oneOf) 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 auf mixed zu kollabieren – reichhaltige Listenfilter sind der häufige Fall und ihre Typen zu verlieren, macht den Sinn eines typisierten SDKs zunichte. Sie fällt nur auf mixed zurück, wenn ein Member keinen eigenen PHP-Typ hat (ein Inline-Objekt, eine Intersection, unknown), weil mixed nicht innerhalb einer Union auftreten kann. Nullability wird als |null in einer Union ausgedrückt (PHP verbietet die Mischung von ? mit |) und ?T für einen einzelnen Typ.
  • Fehler: Exceptions SIND der Fehlermodus (ApiError/TimeoutError erweitern \RuntimeException); errorMode ändert die Ausgabe nicht (der Generator deklariert errorModes: ['throw'], also schlägt result schnell fehl).
  • Daten: dateType: Date typisiert format: date/date-time als \DateTimeImmutable; Hydrierung ist new \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 $headers und ?string $idempotencyKey bei mutierenden Methoden.
  • Nicht-JSON-Erfolgsbodies (PDFs, Bilder, Oktett-Streams) geben den rohen Body als string zurück – ein binärer Download darf niemals auf void zurückfallen.
  • PHPDoc trägt, was die Signatur nicht kann. PHP's array und \Generator lö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 ein Envelope zurückgibt (data, headers – konvertiert zu int/bool/string mit camelCase-Keys, fehlende/nicht parshare Werte weggelassen – und status). 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 bleibt servers[0] mit substituierten Variablen-Standardwerten.
  • Stream-Bodies werden durchgereicht. Bei einer untypisierten multipart/form-data-Operation (isUntypedMultipartBody aus dem Toolkit: ein Schema, das weder ein Objekt noch ein $ref ist) oder einer binären Operation (isBinaryContentType), ist der Body mixed und die Laufzeit klärt es (multipartBody / binaryBody): Eine Stream-resource wird von curl in Chunks hochgeladen (CURLOPT_UPLOAD + CURLOPT_INFILE, die Methode beibehalten von CURLOPT_CUSTOMREQUEST), niemals in einen String gelesen, in genau einem Versuch, unabhängig von der Wiederholungsrichtlinie, weil ein verbrauchter Stream nicht wiederholt werden kann. CURLOPT_INFILESIZE wird nur gesetzt, wenn der Stream seekbar IST UND fstat ein Array zurückgibt (die Bytes ab der aktuellen Position); php://input, Pipes und Wrapper ohne stream_stat gehen chunked. Ein string-Body einer dieser Operationen wird roh gesendet, nicht json_encoded; jeder andere binäre Operationswert wird weiterhin json_encoded; ein array-Body einer untypisierten Multipart-Operation behält den toMultipart-Pfad. Der Content-Type des Aufrufers gewinnt über den der Beschreibung oder der Multipart-Grenze, case-insensitiv in send gematcht, sodass der Header nie zweimal gesendet wird; diese Regel gilt für jede Operation. Das ganze-Request-curl-Time-out gilt weiterhin (timeout: null deaktiviert 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.php in diesem Ordner (php -l-sauber) und wird zur Vorbereitungszeit eingebettet. curl_close wird nie aufgerufen (seit PHP 8.5 deprecated, seit 8.0 ein No-Op). Unter --runtime module wird sie als eine runtime.php geschrieben, die der Client require_oncet, mit ihrem Namespace auf den des Clients umgeschrieben, sodass ein Namespace beide Dateien umspannt.
  • onSettled sieht jeden gesendeten Aufruf genau einmal. send() ruft jeden Config->onSettled-Callable, dann jeden Config::$registeredOnSettled-Callable auf, nach der Wiederholungsschleife mit operationId, status (0 ohne Response), timedOut, attempts und durationMs, 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 an error_log. SSE-Streams öffnen außerhalb von send(), daher erreichen sie keinen Hook.
  • Telemetrie ist zur Generierungszeit opt-in. Mit client.telemetry: on schreibt der Generator auch <client>.telemetry.php (von telemetry/telemetry.php in 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 übergibt new UsageTelemetry(...) in Config->onSettled. Ein SDK-Besitzer ruft register() stattdessen auf, was die Instanz zu Config::$registeredOnSettled hinzufü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->body abgebildet.

  • 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 der docs-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 eigenen sample- Hook dieses Generators, sodass die Seite nur die Syntax des SDKs daneben zeigen kann, und das Layout stammt von renderReferencePage im Authoring-Toolkit – erreichbar von einer ausgekoppelten Kopie über @redocly/client-generator. Die Paginierung auf der Seite wird durch paginationRuleFor entschieden, denselben Helfer, mit dem dieser Generator die Paginierung auflöst.

Der Änderungs-Workflow

  1. Bearbeiten Sie diese Fähigkeit: Beschreiben Sie das neue Verhalten oder die neue Entscheidung.
  2. Lassen Sie generators/php/ damit übereinstimmen.
  3. Führen Sie redocly generate-client aus und prüfen Sie den git diff der generierten Ausgabe – generierte Dateien werden niemals von Hand bearbeitet.

Neuere eingebaute Versionen werden mit redocly eject-generator php --update eingespielt.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Verwandte Skills