Skip to content

Inheritance Reference โ€‹

This page documents the decision rules and mechanics of how PHP class hierarchy maps to OpenAPI composition in the spec pipeline. For usage examples see Using Spec Attributes.

Two augmenters handle inheritance: Inheritance (schema composition via allOf) and PathItems (prefix composition and metadata cloning).

Schema Inheritance โ€‹

Augmenter: OpenApi\Augmenter\Inheritance ยท Phase: Resolve

The Inheritance augmenter walks the PHP class hierarchy for every schema that has a class reflector and expands it into OpenAPI allOf composition or inline property merging.

Decision rules โ€‹

For each schema, the augmenter processes three relationship types in order:

  1. Parents โ€” walks up getParentClass() chain
  2. Traits โ€” direct traits of the class, then traits of non-schema ancestors
  3. Interfaces โ€” direct interfaces of the class

For each ancestor encountered:

Ancestor has #[Schema]?ActionContinue walking?
YesAdd $ref to the schema's allOfStop (parents only)
NoMerge ancestor's #[OA\Property] members inlineContinue

Parent chain walk โ€‹

class C extends B extends A
  • If B has a schema โ†’ C.allOf gets $ref: B, walk stops
  • If B has no schema but A does โ†’ B's properties merge into C, C.allOf gets $ref: A
  • If neither has a schema โ†’ both B's and A's properties merge into C

The "stop at first schema ancestor" rule prevents redundant references โ€” B's schema already composes A if needed.

Trait handling โ€‹

Traits are collected from two sources:

  1. Direct traits of the schema's class
  2. Traits of non-schema ancestors โ€” the augmenter walks up the parent chain until it hits an ancestor with a schema and collects traits from each non-schema ancestor along the way

This ensures that when a non-schema parent uses a trait with #[Schema], the composition is still captured.

Interface handling โ€‹

Only direct interfaces of the schema's class are processed. Unlike parents, there is no stop-on-first-schema rule โ€” all direct interfaces with schemas contribute a $ref.

Property merging โ€‹

When an ancestor has no schema, its #[OA\Property] members are merged into the current schema. Deduplication is by property name โ€” if the schema already declares a property with the same name, the ancestor's version is skipped.

Everything merged from ancestors is prepended to the schema's own properties as a single block, in the order the augmenter visited it: parents from nearest to root, then the class's direct traits in use order, then the traits of each non-schema ancestor, then interfaces. The schema's own properties come last.

allOf restructuring โ€‹

After expansion, if a schema has both allOf entries and its own properties, the Refs augmenter (mergeAllOf()) moves the properties into a dedicated allOf entry โ€” an anonymous schema with type: object โ€” so the result is a pure allOf composition:

yaml
User:
  allOf:
    - $ref: '#/components/schemas/BaseModel'
    - type: object
      properties:
        email:
          type: string

Duplicate $ref deduplication โ€‹

If you explicitly declare an allOf entry that matches one the augmenter would add (e.g. you extend a class and also manually reference it), the Refs augmenter (dedupAllOfRefs()) deduplicates โ€” only one $ref survives. Deduplication runs after class-strings are resolved, so ref: Parent::class and #/components/schemas/parent count as the same entry.

PathItem Inheritance โ€‹

Augmenter: OpenApi\Augmenter\PathItems ยท Phase: Resolve

The PathItems augmenter resolves how #[OA\PathItem] attributes on controller classes compose via PHP inheritance to build operation paths and share metadata.

Prefix composition โ€‹

Each PathItem may declare a prefix. The augmenter composes prefixes by walking up the class hierarchy:

php
#[OA\PathItem(prefix: '/api/v1')]
class BaseController {}

#[OA\PathItem(prefix: '/users')]
class UserController extends BaseController {}

Resolution walks from the class to root, collects prefixes in ancestor order (root first), and joins them:

/api/v1 + /users โ†’ /api/v1/users

The resolved prefix is prepended to each operation's path. An operation with path: '/{id}' in UserController becomes /api/v1/users/{id}.

Governing PathItem โ€‹

An operation's "governing" PathItem is found by walking up from the operation's declaring class until a class with #[PathItem] is found. Operations in a class without #[PathItem] can still inherit from an ancestor's PathItem.

Asking for the chain โ€‹

The walk is Specification\PathItemHierarchy, reached by Specification::buildPathItemHierarchy():

php
$hierarchy = $specification->buildPathItemHierarchy();

$hierarchy->chain(UserController::class);   // every governing PathItem, outermost ancestor first
$hierarchy->governing(UserController::class); // just the nearest one
$hierarchy->forOperation($operation);       // the chain for the class an operation is declared in

Anything read off a PathItem outside that chain applies to nothing and says nothing while it does, so an augmenter or integration reading path-level metadata asks the hierarchy rather than reflecting for itself.

Parent classes only. Traits and interfaces are not followed, which is the opposite of schema inheritance above. The difference is deliberate: a prefix chain is ordered, and a trait or interface graph offers no order to compose in. A PathItem on a trait or an interface is still indexed โ€” it simply never governs the classes using it.

Metadata cloning โ€‹

The augmenter clones metadata from PathItem (and its ancestors) to operations:

PropertyMerge behavior
tagsAccumulated from all ancestors, deduplicated, appended to operation's existing tags
securityAccumulated from all ancestors, deduplicated by scheme name
responsesAccumulated from all ancestors, deduplicated by response code โ€” operation's own responses take precedence

All three accumulate additively up the hierarchy โ€” every ancestor's PathItem contributes.

Path-level parameters โ€‹

PathItem parameters are inherited from ancestor PathItems and are emitted at the path level in the OpenAPI output. Deduplication is by name + in combination. The child's parameters take precedence over ancestors.

Path-level output โ€‹

A PathItem with parameters, summary, description, or servers produces path-level output in the OpenAPI document. The augmenter resolves which operation paths map to this PathItem and emits the path-level properties for each.