O gerador php — sua habilidade
Este arquivo é o DESIGN do seu gerador php ejetado (generators/php/):
para alterar o gerador, edite esta habilidade primeiro, depois faça o código corresponder a ela — uma diferença
para generators/php/ que não tenha uma sentença de cobertura aqui está incompleta.
O que ele emite
Um único arquivo <stem>.php autossuficiente: classes de modelo com construtor promovido, um Client com um
método tipado por operação e o runtime embutido. PHP ≥ 8.1, HTTP sobre a extensão curl — zero dependências Composer. O namespace deriva do título da API
(identifierFor(title, pascal) — ex.: CafeOrders).
Decisões de design que devem ser mantidas
- Modelos são
final classcom promoção de propriedades do construtor, parâmetros obrigatórios primeiro, opcionais anuláveis= null. A hidratação é gerada em tempo de compilação por classe:fromArray(array $data): selfetoArray(): array(nomes de campo inline; nulos ignorados na serialização) — sem reflection. Esquemasomithidratam/serializam através de sua classe base. Uma propriedade ou resposta tipada como uma união DISCRIMINADA hidrata através do despachanteunmarshalXda união, para que consumidores possam restringir cominstanceof; seu tipo incluiarray, porque um valor com um discriminador desconhecido permanece como array bruto. Qualquer outra união permanece como JSON decodificado, então seu tipo é o do JSON: um membro de classe éarray, um membro de enum é seu escalar de suporte e um membro de data éstring. - O arquivo passa no PHPStan nível 5. Ele cai no projeto do próprio usuário, então seu
analisador o lê. A tabela de operações é uma constante que o PHPStan conhece exatamente, então um método
lê
$op['security']apenas quando a tabela tem essa entrada e passa[]caso contrário. - Uma propriedade
writeOnlyé opcional mesmo quando obrigatória: requisições e respostas compartilham uma classe, e uma resposta nunca a carrega. Ela é anulável= null, entãofromArrayhidrata uma resposta sem ela etoArrayainda a envia em uma requisição. - A classe
ClientNÃO éfinal— suítes de teste PHP simulam classes concretas (createMock(Client::class)), efinalforçaria uma interface wrapper em todo consumidor. Classes de modelo permanecemfinal. - Cada parâmetro é seu próprio argumento, então seus nomes compartilham um namespace com os
argumentos que o próprio método declara (
$body,$headers,$idempotencyKey). Construa-os comuniqueIdentifiers(..., { taken: … }): o OpenAPI permite que uma operação use um nome em dois locais (idno caminho E na query), e o PHP rejeita um parâmetro redefinido diretamente. O nome na rede não é alterado, então a requisição permanece inalterada. - O mapa de operações é uma constante de classe,
Client::OPERATIONS, lida através deself::OPERATIONS[...], nunca umaconstno nível do arquivo. O OPcache pré-carregado mantém classes e funções em memória compartilhada, mas descarta constantes no nível do arquivo, e um autoloader nunca inclui o arquivo novamente quandoClientexiste — então um mapa no nível do arquivo é indefinido em toda requisição após a primeira. Uma constante de classe é armazenada com a classe. - Nomenclatura: classes PascalCase, propriedades/métodos camelCase via
identifierFor(..., RESERVED_WORDS.php); palavras reservadas recebem um sublinhado final. - Enums são enums nativos com suporte (string/int); outros escalares permanecem como aliases.
Uniões discriminadas são despachantes
unmarshalX(array $data)baseados emmatch. Cada braço hidrata seu alvo de mapeamento da mesma forma que uma propriedade desse tipo faria, então um alvo sem classe própria (umoneOfsimples) permanece como array bruto; allOf é achatado. - Uniões mantêm seus tipos onde o PHP 8.1 pode expressá-los. Uma união de escalares, enums,
classes ou arrays se torna um tipo de união nativo (
int|string,string|array) em vez de colapsar paramixed— listas de filtro ricas são o caso comum e perder seus tipos perde o propósito de um SDK tipado. Ele recai paramixedapenas quando um membro não tem tipo PHP próprio (um objeto inline, uma intersecção,unknown), porquemixednão pode aparecer dentro de uma união. A anulabilidade é expressa como|nullem uma união (o PHP proíbe misturar?com|) e?Tpara um tipo único. - Erros: exceções SÃO o modo de erro (
ApiError/TimeoutErrorestendem\RuntimeException);errorModenão altera a saída (o gerador declaraerrorModes: ['throw'], entãoresultfalha rapidamente). - Datas:
dateType: Datetipificaformat: date/date-timecomo\DateTimeImmutable; a hidratação énew \DateTimeImmutable(...)e a serialização formata com\DateTimeInterface::ATOM(date-time) ou'Y-m-d'(date), inclusive para parâmetros de query. - Argumentos de método: parâmetros de caminho obrigatórios posicionais, corpo JSON em seguida, parâmetros de query opcionais como argumentos NOMEADOS anuláveis, depois
?array $headerse?string $idempotencyKeyem métodos mutantes. - Corpos de sucesso não-JSON (PDFs, imagens, fluxos octet) retornam o corpo bruto como
string— um download binário nunca deve degradar paravoid. - PHPDoc carrega o que a assinatura não pode. O
arraydo PHP e\Generatorapagam seus tipos de elementos, então um docblock declara:@return Customer[]para retornos de coleção e@return \Generator<int, Customer>em<op>Pages()/<op>Items(). Análise estática e leitores se guiam por isso; um retorno hidratado sem anotação parece não tipado. - Cabeçalhos de resposta: uma operação que DECLARA cabeçalhos de resposta de sucesso ganha uma
variante
<op>WithHeaders()retornando umEnvelope(data,headers— convertidos para int/bool/string com chaves camelCase, valores ausentes/inescrutáveis omitidos — estatus). Operações sem cabeçalhos declarados não recebem variante, e o método base permanece apenas com corpo (o PHP não pode variar um tipo de retorno em uma flag). - Servidores: quando a descrição declara servidores, uma classe
Serversé emitida com um método estático por servidor; VARIÁVEIS de servidor se tornam argumentos string nomeados com padrão para os padrões da especificação (Servers::production(organizationId: 'org_x')), então URLs base com template não precisam de construção manual de string. O padrão embutido do cliente permaneceservers[0]com substituições de variáveis padrão. - Corpos de stream passam através. Para uma operação
multipart/form-datanão tipada (isUntypedMultipartBodydo toolkit: um esquema que não é nem objeto nem$ref) ou uma operação binária (isBinaryContentType), o corpo émixede o runtime resolve (multipartBody/binaryBody): umresourcede stream é enviado pelo curl em chunks (CURLOPT_UPLOAD+CURLOPT_INFILE, o método mantido porCURLOPT_CUSTOMREQUEST), nunca lido em uma string, em exatamente uma tentativa, qualquer que seja a política de retry, porque um stream consumido não pode ser reproduzido.CURLOPT_INFILESIZEé definido apenas quando o stream é seekable Efstatretorna um array (os bytes restantes da posição atual);php://input, pipes e wrappers semstream_statvão em chunked. Um corpostringde qualquer operação é enviado bruto, nãojson_encoded; qualquer outro valor de operação binária é aindajson_encoded; um corpoarrayde uma operação multipart não tipada mantém o caminhotoMultipart. OContent-Typedo chamador vence sobre o da descrição ou do limite multipart, combinado case-insensitively emsendpara que o cabeçalho nunca seja enviado duas vezes; essa regra vale para toda operação. O timeout curl de toda a requisição ainda se aplica (timeout: nullo desabilita). Corpos multipart tipados não são afetados. - Superfície de paridade: autenticação, retries com
Retry-After+ backoff com jitter, timeouts curl por tentativa, callables de middleware, paginação (<op>Pages()/<op>Items()como\Generators), SSE (iterSsesobre uma bomba curl_multi), multipart. - O runtime é escrito à mão em
runtime/runtime.phpnesta pasta (limpo comphp -l) e embutido em tempo de preparação.curl_closenunca é chamado (obsoleto desde PHP 8.5, no-op desde 8.0). Sob--runtime moduleele é escrito como umruntime.phpque o clienterequire_once, com seu namespace reescrito para o namespace do cliente para que um namespace abranja ambos os arquivos. onSettledvê cada chamada enviada uma vez.send()chama cada callableConfig->onSettled, depois cada callableConfig::$registeredOnSettled, após o loop de retry comoperationId,status(0 sem resposta),timedOut,attemptsedurationMs, antes de lançar ou retornar. Todo hook é executado. O primeiro erro de hook é lançado para uma chamada bem-sucedida; em uma chamada falha, o erro original vence e o erro do hook vai paraerror_log. Streams SSE abrem fora desend(), então eles não atingem nenhum hook.- Telemetria é opt-in em tempo de geração. Com
client.telemetry: ono gerador também escreve<client>.telemetry.php(detelemetry/telemetry.phpnesta pasta, embutido em tempo de preparação) ao lado do cliente, em ambos os modos de runtime, com o namespace do cliente e a versão do gerador embutidos. O arquivo do cliente não muda. A aplicação o requer e passanew UsageTelemetry(...)emConfig->onSettled. Um proprietário de SDK chamaregister()em vez disso, que adiciona a instância aConfig::$registeredOnSettled, então todo cliente conta suas chamadas;UsageTelemetry::flushRegistered()libera cada instância registrada. PHP não tem timers: uma chamada que encontra sua janela expirada a envia primeiro, e uma função de shutdown envia a janela aberta. Constantes vivem na classe, porque o pré-carregamento OPcache descarta constantes no nível do arquivo. O nome da classe é reservado para todo SDK, porque o arquivo compartilha o namespace com os modelos. - Criado APENAS com o toolkit neutro — o guardião de dogfooding falha caso contrário.
Migrando de um SDK baseado em serviço
-
Serviços por recurso (
$client->customers()->get($id)) mapeiam para métodos planos nomeados após operationIds ($client->getCustomer($id)); parâmetros de query opcionais mantêm seu estilo de argumento nomeado (filter:,sort:,limit:). -
Wrappers de coleção expondo CABEÇALHOS DE RESPOSTA de paginação (
getTotalItems(),getLimit()) mapeiam para o envelope<op>WithHeaders()(->headers['paginationTotal']); iteração simples mapeia para geradores<op>Items()/<op>Pages(). -
Classes de exceção de validação dedicadas expondo erros de campo mapeiam para
catch (ApiError $e)+$e->status === 422+ o$e->bodydecodificado. -
Fluxos de token de sessão/bearer mapeiam para
auth: ['bearer' => $tokenProvider]com um callable — resolvido por requisição, então refresh não precisa de reconstrução do cliente. -
Ele se documenta. Com
client.docs(ou--docs), o hookdocsescreve<stem>.php.md: os esquemas de segurança, depois uma seção por operação com seus parâmetros, corpo, tipo de resposta e notas de comportamento. Os trechos de chamada vêm do próprio hooksampledeste gerador, então a página pode mostrar apenas a sintaxe do SDK ao lado, e o layout vem derenderReferencePageno toolkit de autoria — acessível de uma cópia ejetada através de@redocly/client-generator. A paginação na página é decidida porpaginationRuleFor, o mesmo helper com o qual este gerador resolve paginação.
O loop de modificação
- Edite esta habilidade: declare o novo comportamento ou decisão.
- Faça
generators/php/corresponder a ele. - Execute
redocly generate-cliente inspecione ogit diffda saída gerada — arquivos gerados nunca são editados manualmente.
Versões embutidas mais novas são mescladas com redocly eject-generator php --update.