Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ it and getting people onto it:
* a rector rule set for `OpenApi\Attributes` -> `OpenApi\Spec`
* a seam to contribute pre-built attributes to a `Specification` before resolution, for
framework integrations that do not scan
* one `component:` field on every reusable `Spec` attribute, naming its key in `components`;
the per-type spellings it replaces (`schema`, `parameter`, `request`, `securityScheme`, and
`response`/`header`/`link`/`example` used as a component key) are marked `@deprecated`,
removed in `8.0`, and trigger a runtime deprecation when used
* docblock annotation support marked `@deprecated`, removed in `8.0` - the README has said so
since 4.8, the code now agrees, and parsing a docblock annotation triggers a runtime
deprecation once per run
Expand Down
20 changes: 13 additions & 7 deletions docs/dev/pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,19 @@ attribute is one that can stand alone — it owns a bucket and needs no parent.

- **Always root**: `Schema`, `Operation`, `PathItem`, `OpenApi`, `Info`, `Tag`, `Server`,
`ExternalDocumentation`, `Security\Scheme`, `Components`, `Attachable`
- **Conditionally root**, when their own key is set and `ref` is not: `Response`
(`response`), `Parameter` (`parameter`), `Link` (`link`); `RequestBody` needs `request`
set but has no `ref` check
- **Never root**: `Header`, `Example`, `MediaType`, `Property` — these must nest inside a
parent or sit in a `Components` container

Each attribute decides for itself, in `isRoot()`.
- **Conditionally root**, when `component` is set — the key the attribute is filed under in
`components` — and `ref` is not: `Response`, `Parameter`, `Link`, `RequestBody`, `Header`,
`Example`, `MediaType`. The request body has no `ref` check, since its key can never double
as a nesting key. Until 8.0 the historic spellings still count for the response and the
link (`response`, `link`); the other historic spellings alias onto `component` in the
constructor and need no clause
- **Never root**: `Property` — it must nest inside a parent

Each attribute decides for itself, in `isRoot()`. The one field behind the conditional rule,
`component`, is read through `Specification\ComponentName::of()` everywhere a key is needed —
the compiler, `ComponentIndex`, `Augmenter\Cleanup` — and filled in from the historic
spellings once per build by `ComponentName::normalise()`, after the contribution hooks and
before the resolver builds the first index.

Root does not mean un-nested. `isRoot()` says what an attribute may be when nothing consumes
it; `merge()` and `contained()` run first, and often do. `Schema` is always root, and is
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/api/hybrid/NameTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* A Name.
*/
#[OA\Schema(schema: 'NameTrait')]
#[OA\Schema(component: 'NameTrait')]
trait NameTrait
{
#[OA\Property(property: 'name')]
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/misc/spec/ResultSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'Result', title: 'Sample schema for using references')]
#[OA\Schema(title: 'Sample schema for using references', component: 'Result')]
class ResultSchema
{
#[OA\Property]
Expand Down
25 changes: 10 additions & 15 deletions docs/examples/specs/petstore/spec/Models/PetRequestBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,16 @@

use OpenApi\Spec as OA;

#[OA\RequestBody(
request: 'Pet',
description: 'Pet object that needs to be added to the store',
required: true,
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: Pet::class),
),
new OA\MediaType(
mediaType: 'application/xml',
schema: new OA\Schema(ref: Pet::class),
),
],
)]
#[OA\RequestBody(description: 'Pet object that needs to be added to the store', required: true, content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: Pet::class),
),
new OA\MediaType(
mediaType: 'application/xml',
schema: new OA\Schema(ref: Pet::class),
),
], component: 'Pet')]
class PetRequestBody
{
}
17 changes: 6 additions & 11 deletions docs/examples/specs/petstore/spec/Models/UserArrayRequestBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@

use OpenApi\Spec as OA;

#[OA\RequestBody(
request: 'UserArray',
description: 'List of user object',
required: true,
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(type: 'array', items: new OA\Schema(ref: User::class)),
),
],
)]
#[OA\RequestBody(description: 'List of user object', required: true, content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(type: 'array', items: new OA\Schema(ref: User::class)),
),
], component: 'UserArray')]
class UserArrayRequestBody
{
}
4 changes: 2 additions & 2 deletions docs/examples/specs/polymorphism/spec/AbstractResponsible.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'Responsible', oneOf: [
#[OA\Schema(oneOf: [
new OA\Schema(ref: Fl::class),
new OA\Schema(ref: Employee::class),
], discriminator: new OA\Discriminator(
Expand All @@ -17,7 +17,7 @@
'fl' => Fl::class,
'employee' => Employee::class,
],
))]
), component: 'Responsible')]
abstract class AbstractResponsible
{
protected const TYPE = null;
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/polymorphism/spec/Employee.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'EmployeeResponsible')]
#[OA\Schema(component: 'EmployeeResponsible')]
final class Employee extends AbstractResponsible
{
#[OA\Property(property: 'type')]
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/polymorphism/spec/Fl.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'FlResponsible')]
#[OA\Schema(component: 'FlResponsible')]
final class Fl extends AbstractResponsible
{
public const TYPE = 'fl';
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/using-links/spec/PullRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'pullrequest')]
#[OA\Schema(component: 'pullrequest')]
class PullRequest
{
#[OA\Property]
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/using-links/spec/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'repository')]
#[OA\Schema(component: 'repository')]
class Repository
{
#[OA\Property]
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/specs/using-links/spec/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'user')]
#[OA\Schema(component: 'user')]
class User
{
#[OA\Property]
Expand Down
9 changes: 1 addition & 8 deletions docs/examples/specs/using-refs/spec/ProductParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@
#[OA\Components]
class ProductParameter
{
#[OA\Parameter(
parameter: 'product_id_in_path_required',
name: 'product_id',
in: OA\ParameterIn::Path,
description: 'The ID of the product',
required: true,
schema: new OA\Schema(type: 'integer', format: 'int64'),
)]
#[OA\Parameter(name: 'product_id', in: OA\ParameterIn::Path, description: 'The ID of the product', required: true, schema: new OA\Schema(type: 'integer', format: 'int64'), component: 'product_id_in_path_required')]
public int $product_id;
}
13 changes: 4 additions & 9 deletions docs/examples/specs/using-refs/spec/ProductRequestBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@

use OpenApi\Spec as OA;

#[OA\RequestBody(
request: 'product_in_body',
description: 'product_request',
required: true,
content: [new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: '#/components/schemas/Product'),
)],
)]
#[OA\RequestBody(description: 'product_request', required: true, content: [new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: '#/components/schemas/Product'),
)], component: 'product_in_body')]
class ProductRequestBody
{
}
12 changes: 4 additions & 8 deletions docs/examples/specs/using-refs/spec/ProductResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@

use OpenApi\Spec as OA;

#[OA\Response(
response: 'product',
description: 'All information about a product',
content: [new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: '#/components/schemas/Product'),
)],
)]
#[OA\Response(description: 'All information about a product', content: [new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(ref: '#/components/schemas/Product'),
)], component: 'product')]
class ProductResponse
{
}
2 changes: 1 addition & 1 deletion docs/examples/specs/using-refs/spec/ProductStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'product_status', description: 'The status of a product', type: 'string', enum: ['available', 'discontinued'], default: 'available')]
#[OA\Schema(description: 'The status of a product', type: 'string', enum: ['available', 'discontinued'], default: 'available', component: 'product_status')]
class ProductStatus
{
}
5 changes: 1 addition & 4 deletions docs/examples/specs/using-refs/spec/TodoResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Response(
response: 'todo',
description: 'This API call has no documentated response (yet)',
)]
#[OA\Response(description: 'This API call has no documentated response (yet)', component: 'todo')]
class TodoResponse
{
}
2 changes: 1 addition & 1 deletion docs/examples/specs/using-traits/spec/Blink.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use OpenApi\Spec as OA;

#[OA\Schema(schema: 'CustomName-Blink', title: 'Blink trait')]
#[OA\Schema(title: 'Blink trait', component: 'CustomName-Blink')]
trait Blink
{
/**
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ The recommended migration path is:

1. **Classic → Hybrid** — change `setMode(Mode::HYBRID)` and verify output is unchanged. No code changes needed. This gives you access to the augmenter pipeline.

2. **Hybrid → Spec** — when starting new code, use `OpenApi\Spec` attributes. Existing `OpenApi\Attributes` code continues to work via hybrid mode.
2. **Hybrid → Spec** — when starting new code, use `OpenApi\Spec` attributes. Existing `OpenApi\Attributes` code continues to work via hybrid mode. The spec attributes are not a one-for-one rename of the classic ones: a reusable attribute takes a single `component:` key where classic spells the key after its own type (`schema:`, `parameter:`, `request:`, `securityScheme:`). See [Components](/guide/spec-attributes#components).

3. **Full Spec** — once all code uses `OpenApi\Spec` attributes, switch to `setMode(Mode::SPEC)`.

::: tip Version timeline
- **v6** — spec/hybrid ship as opt-in beta. Classic remains default.
- **v7** — hybrid becomes the default mode. Classic still available. `setMode()` and all classic code deprecated.
- **v8** — classic removed. `setMode()` removed. Spec becomes default. Spec attributes move to `OpenApi\Attributes`, one `use` line per file.
- **v8** — classic removed. `setMode()` removed. Spec becomes default. Spec attributes move to `OpenApi\Attributes`. For code already written against `OpenApi\Spec` that move is one `use` line per file; coming from classic, the component keys are renamed as well.
:::
56 changes: 38 additions & 18 deletions docs/guide/spec-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,40 +339,60 @@ class ValidationErrors {}

## Components

`#[OA\Components]` is a class-level container for reusable definitions that cannot stand alone as root attributes — primarily Parameters, Headers, Links, and Examples.
Any reusable attribute becomes a component by giving it a `component:` key — the name it is
filed under in the document's `components` section, and the name a `$ref` points at.
Schemas, responses, parameters, request bodies, headers, links, examples, security schemes and
path items all take it, and a keyed attribute is a root: it can be declared on a class by
itself, with no wrapper.

```php
use OpenApi\Spec as OA;

#[OA\Components]
class SharedComponents
{
#[OA\Parameter(parameter: 'page', name: 'page', in: 'query')]
#[OA\Schema(type: 'integer', default: 1)]
public int $page;
#[OA\Parameter(component: 'page', name: 'page', in: 'query')]
#[OA\Schema(type: 'integer', default: 1)]
class PageParameter {}

#[OA\Parameter(parameter: 'per_page', name: 'per_page', in: 'query')]
#[OA\Schema(type: 'integer', default: 20)]
public int $perPage;
#[OA\Header(component: 'RateLimit', description: 'Requests remaining')]
#[OA\Schema(type: 'integer')]
class RateLimitHeader {}

#[OA\Header(header: 'X-Rate-Limit', description: 'Requests remaining')]
#[OA\Schema(type: 'integer')]
public string $rateLimit;
}
#[OA\Response(component: 'NotFound', description: 'No such thing')]
class NotFoundResponse {}
```

These can then be referenced from operations via `$ref`:
These are then referenced from operations via `$ref`:

```php
#[OA\Operation\Get(path: '/users', parameters: [
new OA\Parameter(ref: '#/components/parameters/page'),
new OA\Parameter(ref: '#/components/parameters/per_page'),
], responses: [
new OA\Response(response: 404, ref: '#/components/responses/NotFound'),
])]
public function list() {}
```

::: tip When to use Components
Schemas, PathItems, security schemes (`OA\Security\Scheme`), and named Responses/RequestBodies are root attributes — they can be declared directly on a class without a Components wrapper. Use Components only for types that can't stand alone (Parameter, Header, Link, Example).
Note the two keys on that response: `response: 404` is where it nests in the operation, and
`NotFound` is the component it points at. The same split holds for a header (`header:` is the
HTTP header name, `component:` the reusable definition), a link and an example. A schema
declared on a class needs no key at all — it is named after the class.

`#[OA\Components]` remains as a class-level container for declaring several components on one
class, and for the historic spellings below.

::: warning Historic spellings, deprecated
Before 6.11 each type spelled its key after itself: `schema: 'Pet'`, `parameter: 'page'`,
`request: 'Body'`, `securityScheme: 'api'`, and — only when declared as a component —
`response: 'NotFound'`, `header: 'RateLimit'`, `link: 'Self'`, `example: 'Minimal'`. They still
work, produce the same document, and trigger a deprecation; they are removed in 8.0. Only the
component use is deprecated: `response: 404` on a nested response, or `header: 'X-Rate-Limit'`
on a header inside a response, is the nesting key and stays.

Classic is not affected and reports nothing. `OpenApi\Attributes` keeps `schema:`,
`parameter:`, `request:` and `securityScheme:` as its component keys, and hybrid mode
translates them to `component:` through the bridge. What this does change is the price of
moving a classic codebase onto `OpenApi\Spec`: it is no longer a change of `use` line and
nothing else, because every component key is renamed with it. See
[Migration path](/guide/modes#migration-path).
:::

## Inheritance
Expand Down
Loading
Loading