Community코딩 & 개발github.com

Redocly/php-generator

이젝트된 Redocly `php` 클라이언트 생성기의 설계를 설명합니다. 생성기를 변경하기 전에 이 스킬을 읽고 업데이트하세요.

php-generator란 무엇인가요?

이 스킬은 Redocly API 클라이언트 생성기에서 `php` 생성기(`generators/php/`)의 완전한 설계를 정의합니다. 이는 생성기가 PHP ≥ 8.1용으로 완전히 독립적인 `<stem>.php` 파일을 생성하는 방법을 문서화하며, 모델은 `final class`로 생성자 프로퍼티 프로모션을 사용하고, HTTP 통신은 cURL 확장을 통해 수행되며, Composer 종속성이 전혀 없습니다. 설계 결정은 PHPStan 레벨 5 통과, OPcache 호환성을 위한 클래스 상수맵, discriminated union을 위한 `instanceof` 지원, PHP 8.1 고유 유니언 타입을 최대한 유지하는 것, 그리고 `ApiError`/`TimeoutError` 예외 기반 오류 처리를 포함합니다. 이 스킬은 또한 서비스 기반 SDK에서 마이그레이션, 페이지네이션, SSE, 멀티파트 바디 스트리밍, 응답 헤더가 포함된 `Envelope` 패턴, 그리고 선택적인 생성 시간 텔레메트리를 포함한 고급 기능을 다룹니다. 핵심 워크플로우는 이 스킬을 먼저 편집하고, 그 다음 생성기 코드를 일치시키며, `redocly generate-client`를 실행하여 변경 사항을 검증하는 것입니다.

지원 대상~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 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

php 생성기 — 그 스킬

이 파일은 당신이 이젝트한 php 생성기(generators/php/)의 설계입니다: 생성기를 변경하려면 먼저 이 스킬을 편집한 후, 코드가 일치하도록 만드세요 — 커버하는 문장 없이 generators/php/에 대한 diff는 불완전합니다.

무엇을 생성하는가

하나의 자체 포함된 <stem>.php: 프로모티드 생성자 모델 클래스들, 각 연산당 하나의 타입화된 메서드를 가진 Client, 그리고 내장된 런타임. PHP ≥ 8.1, cURL 확장을 통한 HTTP — Composer 종속성 제로. 네임스페이스는 API 제목에서 파생됩니다 (identifierFor(title, pascal) — 예: CafeOrders).

유지되어야 할 설계 결정

  • 모델은 final class입니다 생성자 프로퍼티 프로모션을 사용하며, 필수 매개변수가 먼저 오고, 선택적 매개변수는 nullable = null입니다. 하이드레이션은 클래스별로 컴파일 타임에 생성됩니다: fromArray(array $data): self 및 toArray(): array (와이어 이름은 인라인; null은 직렬화 시 건너뜀) — 리플렉션 없음. omit 스키마는 기본 클래스를 통해 하이드레이트/직렬화됩니다. DISCRIMINATED 유니언으로 타입화된 프로퍼티 또는 응답은 유니언의 unmarshalX 디스패처를 통해 하이드레이트되므로, 소비자는 instanceof로 좁힐 수 있습니다; 그 타입에는 array가 포함됩니다. 왜냐하면 알 수 없는 디스크리미네이터를 가진 값은 원시 배열로 남기 때문입니다. 다른 모든 유니언은 디코딩된 JSON으로 남으므로, 그 타입은 JSON의 타입을 따릅니다: 클래스 멤버는 array, 열거형 멤버는 그 지원 스칼라, 날짜 멤버는 string입니다.
  • 파일은 PHPStan 레벨 5를 통과합니다. 사용자의 자신의 프로젝트에 착지되므로, 그들의 분석기가 읽습니다. 연산 테이블은 PHPStan이 정확히 아는 상수이므로, 메서드는 테이블에 해당 항목이 있을 때만 $op['security']를 읽고, 그렇지 않으면 []를 전달합니다.
  • writeOnly 프로퍼티는 필수인 경우에도 선택적입니다: 요청과 응답이 하나의 클래스를 공유하며, 응답은 결코 그것을 전달하지 않습니다. 그것은 nullable = null이므로, fromArray는 그것 없이 응답을 하이드레이트하고 toArray는 여전히 요청에서 그것을 보냅니다.
  • Client 클래스는 final이 아닙니다 — PHP 테스트 스위트는 구체 클래스를 모의합니다 (createMock(Client::class)), 그리고 final은 모든 소비자에게 래퍼 인터페이스를 강제합니다. 모델 클래스는 final로 유지됩니다.
  • 모든 매개변수는 각자의 인수이므로, 그들의 이름은 메서드 자체가 선언하는 인수들과 하나의 네임스페이스를 공유합니다 ($body, $headers, $idempotencyKey). uniqueIdentifiers(..., { taken: … })로 빌드하세요: OpenAPI는 하나의 연산이 두 위치에서 이름을 사용할 수 있게 합니다(경로와 쿼리 모두에서 id), 그리고 PHP는 재정의된 매개변수를 완전히 거부합니다. 와이어 이름은 변경되지 않으므로 요청은 변경되지 않습니다.
  • **연산 맵은 클래스 상수, Client::OPERATIONS**이며, self::OPERATIONS[...]를 통해 읽히고, 파일 레벨 const가 아닙니다. OPcache 프리로드는 클래스와 함수를 공유 메모리에 유지하지만 파일 레벨 상수는 드롭하며, 오토로더는 Client가 존재하는 한 파일을 다시 포함하지 않습니다 — 따라서 파일 레벨 맵은 첫 번째 이후의 모든 요청에서 정의되지 않습니다. 클래스 상수는 클래스와 함께 저장됩니다.
  • 명명: 클래스는 PascalCase, 프로퍼티/메서드는 camelCase (identifierFor(..., RESERVED_WORDS.php) 사용); 예약어는 밑줄을 추가합니다.
  • 열거형은 네이티브 백업드 열거형(string/int)입니다; 다른 스칼라는 별칭으로 남습니다. Discriminated unions은 match 기반의 unmarshalX(array $data) 디스패처입니다. 각 분기는 해당 타입의 프로퍼티가 하는 방식으로 매핑 대상을 하이드레이트하므로, 자체 클래스가 없는 대상(평범한 oneOf)은 원시 배열로 남습니다; allOf는 평탄화됩니다.
  • 유니언은 PHP 8.1이 표현할 수 있는 곳에서 타입을 유지합니다. 스칼라, 열거형, 클래스 또는 배열의 유니언은 mixed로 축소되기보다는 네이티브 유니언 타입(int|string, string|array)이 됩니다 — 풍부한 목록 필터가 일반적인 경우이며 그 타입을 잃는 것은 타입화된 SDK의 요점을 잃는 것입니다. 멤버가 자체 PHP 타입이 없을 때만(인라인 객체, 교차, unknown) mixed로 폴백됩니다. 왜냐하면 mixed는 유니언 내부에 나타날 수 없기 때문입니다. Nullability는 유니언에서 |null로 표현되고(PHP는 ?와 |를 혼합하는 것을 금지), 단일 타입에 대해 ?T로 표현됩니다.
  • 오류: 예외가 오류 모드입니다 (ApiError/TimeoutError는 \RuntimeException을 확장); errorMode는 출력을 변경하지 않습니다 (생성기는 errorModes: ['throw']를 선언하므로, result는 빠르게 실패합니다).
  • 날짜: dateType: Date는 format: date/date-time을 \DateTimeImmutable로 타입화합니다; 하이드레이션은 new \DateTimeImmutable(...)이고 직렬화는 \DateTimeInterface::ATOM(date-time) 또는 'Y-m-d'(date) 형식을 사용합니다, 쿼리 매개변수도 포함.
  • 메서드 인수: 필수 경로 매개변수는 위치 기반, JSON 바디는 다음, 선택적 쿼리 매개변수는 nullable NAMED 인수, 그 다음 ?array $headers, 그리고 변경 메서드에는 ?string $idempotencyKey.
  • 비-JSON 성공 바디 (PDF, 이미지, octet 스트림)는 원시 바디를 string으로 반환합니다 — 바이너리 다운로드는 절대 void로 저하되어서는 안 됩니다.
  • PHPDoc은 시그니처가 표현할 수 없는 것을 전달합니다. PHP의 array와 \Generator는 요소 타입을 지우므로, docblock이 명시합니다: 컬렉션 반환에는 @return Customer[], <op>Pages()/<op>Items()에는 @return \Generator<int, Customer>. 정적 분석과 읽는 사람은 이것을 따릅니다; 주석이 없는 하이드레이티드 반환은 타입이 없는 것처럼 보입니다.
  • 응답 헤더: 성공 응답 헤더를 DECLARE하는 연산은 Envelope(data, headers — int/bool/string으로 강제 변환되며 camelCase 키, 없거나 파싱 불가능한 값은 생략 — 및 status)를 반환하는 <op>WithHeaders() 변형을 얻습니다. 선언된 헤더가 없는 연산은 변형을 얻지 못하며, 기본 메서드는 바디 전용으로 유지됩니다(PHP는 플래그에 따라 반환 타입을 변경할 수 없습니다).
  • 서버: 설명이 서버를 선언하면, Servers 클래스가 서버당 하나의 정적 메서드와 함께 생성됩니다; 서버 VARIABLES는 스펙의 기본값으로 기본 설정된 명명된 문자열 인수가 됩니다 (Servers::production(organizationId: 'org_x')), 따라서 템플릿화된 기본 URL은 수동 문자열 빌드가 필요하지 않습니다. 클라이언트의 내장된 기본값은 변수 기본값이 대체된 servers[0]로 유지됩니다.
  • 스트림 바디는 통과합니다. 타입화되지 않은 multipart/form-data 연산(isUntypedMultipartBody from the toolkit: 객체도 $ref도 아닌 스키마) 또는 바이너리 연산(isBinaryContentType)의 경우, 바디는 mixed이고 런타임이 처리합니다 (multipartBody / binaryBody): 스트림 resource는 cURL에 의해 청크로 업로드됩니다 (CURLOPT_UPLOAD + CURLOPT_INFILE, CURLOPT_CUSTOMREQUEST로 유지되는 메서드), 절대 문자열로 읽히지 않으며, 정확히 한 번 시도되며, 재시도 정책에 관계없이, 소비된 스트림은 재생될 수 없기 때문입니다. CURLOPT_INFILESIZE는 스트림이 seekable하고 fstat가 배열을 반환하는 경우에만 설정됩니다(현재 위치에서 남은 바이트); php://input, 파이프, stream_stat가 없는 래퍼는 chunked로 전송됩니다. 두 연산 중 하나의 string 바디는 원시로 전송되며, json_encode되지 않습니다; 다른 바이너리 연산 값은 여전히 json_encode됩니다; 타입화되지 않은 multipart 연산의 array 바디는 toMultipart 경로를 유지합니다. 호출자의 Content-Type은 설명 또는 multipart 경계의 것보다 우선하며, send에서 대소문자 구분 없이 일치되어 헤더가 두 번 전송되지 않도록 합니다; 그 규칙은 모든 연산에 적용됩니다. 전체 요청 cURL 타임아웃은 여전히 적용됩니다 (timeout: null은 비활성화). 타입화된 multipart 바디는 영향을 받지 않습니다.
  • 패리티 표면: 인증, Retry-After + 지터 백오프를 사용한 재시도, 시도당 cURL 타임아웃, 미들웨어 콜러블, 페이지네이션 (<op>Pages() / <op>Items()를 \Generator로), SSE (curl_multi 펌프를 통한 iterSse), multipart.
  • 런타임은 이 폴더의 runtime/runtime.php에 수작업으로 작성되며(php -l-클린) 준비 시간에 내장됩니다. curl_close는 절대 호출되지 않습니다 (PHP 8.5부터 사용 중단, 8.0부터 no-op). --runtime module에서는 클라이언트가 require_once하는 runtime.php로 작성되며, 네임스페이스는 클라이언트의 네임스페이스로 다시 작성되어 한 네임스페이스가 두 파일을 포괄합니다.
  • onSettled는 전송된 모든 호출을 한 번 봅니다. send()는 재시도 루프 후에 operationId, status(응답 없으면 0), timedOut, attempts, durationMs와 함께 각 Config->onSettled 콜러블, 그 다음 각 Config::$registeredOnSettled 콜러블을 호출하며, 예외를 던지거나 반환하기 전에 호출됩니다. 모든 훅이 실행됩니다. 성공적인 호출에 대해 첫 번째 훅 오류가 던져집니다; 실패한 호출에서는 원래 오류가 우선하고 훅 오류는 error_log로 이동합니다. SSE 스트림은 send() 외부에서 열리므로, 어떤 훅에도 도달하지 않습니다.
  • 텔레메트리는 생성 시간에 선택적으로 활성화됩니다. client.telemetry: on을 사용하면 생성기가 클라이언트 옆에 <client>.telemetry.php도 작성합니다(이 폴더의 telemetry/telemetry.php에서, 준비 시간에 내장됨), 두 런타임 모드 모두에서, 클라이언트의 네임스페이스와 생성기 버전이 내장됩니다. 클라이언트 파일은 변경되지 않습니다. 응용 프로그램은 그것을 require하고 Config->onSettled에 new UsageTelemetry(...)를 전달합니다. SDK 소유자는 대신 register()를 호출하여 인스턴스를 Config::$registeredOnSettled에 추가하므로, 모든 클라이언트가 자신의 호출을 계산합니다; UsageTelemetry::flushRegistered()는 각 등록된 인스턴스를 플러시합니다. PHP에는 타이머가 없습니다: 윈도우가 경과된 호출은 먼저 보내고, 종료 함수가 열린 윈도우를 보냅니다. 상수는 클래스에 존재합니다. 왜냐하면 OPcache 프리로딩이 파일 레벨 상수를 드롭하기 때문입니다. 클래스 이름은 모든 SDK에 대해 예약되어 있습니다. 왜냐하면 파일이 모델과 네임스페이스를 공유하기 때문입니다.
  • 중립 툴킷으로만 작성됨 — 그렇지 않으면 dogfooding 가드가 실패합니다.

서비스 기반 SDK에서 마이그레이션하기

  • 리소스별 서비스 ($client->customers()->get($id))는 operationIds를 따르는 평탄한 메서드에 매핑됩니다 ($client->getCustomer($id)); 선택적 쿼리 매개변수는 명명된 인수 스타일을 유지합니다 (filter:, sort:, limit:).

  • 페이지네이션 응답 헤더를 노출하는 컬렉션 래퍼 (getTotalItems(), getLimit())는 <op>WithHeaders() 봉투에 매핑됩니다 (->headers['paginationTotal']); 단순 반복은 <op>Items() / <op>Pages() 제너레이터에 매핑됩니다.

  • 필드 오류를 노출하는 전용 검증 예외 클래스는 catch (ApiError $e) + $e->status === 422 + 디코딩된 $e->body에 매핑됩니다.

  • 세션/베어러 토큰 흐름은 auth: ['bearer' => $tokenProvider]에 콜러블로 매핑됩니다 — 요청별로 해결되므로, 리프레시에 클라이언트 재빌드가 필요하지 않습니다.

  • 자체 문서화합니다. client.docs(또는 --docs)와 함께, docs 훅은 <stem>.php.md를 작성합니다: 보안 스키마, 그 다음 각 연산당 하나의 섹션과 그 매개변수, 바디, 응답 타입, 동작 노트가 포함됩니다. 호출 스니펫은 이 생성기 자체의 sample 훅에서 오므로, 페이지는 그 옆에 있는 SDK의 구문만 보여줄 수 있으며, 레이아웃은 작성 툴킷의 renderReferencePage에서 옵니다 — 이젝트된 복사본에서 @redocly/client-generator를 통해 접근 가능합니다. 페이지의 페이지네이션은 이 생성기가 페이지네이션을 해결하는 데 사용하는 동일한 헬퍼인 paginationRuleFor에 의해 결정됩니다.

수정 루프

  1. 이 스킬을 편집하세요: 새로운 동작 또는 결정을 명시하세요.
  2. generators/php/를 일치시키세요.
  3. redocly generate-client를 실행하고 생성된 출력의 git diff를 검사하세요 — 생성된 파일은 수동으로 편집되지 않습니다.

새로운 내장 버전은 redocly eject-generator php --update로 병합됩니다.

Individual skills in this repo

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

관련 스킬