radebatz / openapi-json-schema
Describes classes without OpenAPI attributes in the document swagger-php builds, using the schemas symfony/json-schema generates from PropertyInfo, the serializer and validator constraints.
Requires
- php: >=8.2
- phpdocumentor/type-resolver: ^1.10
- phpstan/phpdoc-parser: ^2.0
- symfony/json-schema: 8.2.x-dev
- zircote/swagger-php: ^7.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.62.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- rector/rector: ^2.0
- symfony/serializer: ^7.4 || ^8.0
- symfony/validator: ^7.4 || ^8.0
- symfony/yaml: ^7.4 || ^8.0
Suggests
- symfony/serializer: To describe serializer groups, serialized names and discriminator maps
- symfony/validator: To describe validator constraints
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-06 00:34:39 UTC
README
Introduction
A request or response often uses a class that has no OpenAPI attributes: a DTO, an entity, a class from another package. swagger-php can only describe a class through its attributes, so the class has to be annotated or the reference stays unresolved.
openapi-json-schema describes such a class with the schema
symfony/json-schema generates for it. That schema
comes from what Symfony already knows about the class: property types and docblocks through
PropertyInfo, and with symfony/serializer and symfony/validator installed, serializer groups,
name converters, discriminator maps and validator constraints.
symfony/json-schema is experimental. It ships with Symfony 8.2 and has no backwards
compatibility promise yet. This package follows it and is experimental too. Once the
component is stable, this package might move into swagger-php as an optional part.
Installation
composer require radebatz/openapi-json-schema
The package requires swagger-php 7.3 or later. Until symfony/json-schema has a stable release, the
project needs "minimum-stability": "dev" and "prefer-stable": true in its composer.json.
Usage
JsonSchemaPlugin adds a JsonSchemaResolver to the builder, after swagger-php's own resolver:
use OpenApi\Builder; use OpenApi\Builder\Mode; use Radebatz\OpenApi\JsonSchema\JsonSchemaPlugin; $result = (new Builder()) ->setMode(Mode::SPEC) ->addSource('src') ->withPlugins([JsonSchemaPlugin::class]) ->build();
On the command line, the same is --plugin "Radebatz\OpenApi\JsonSchema\JsonSchemaPlugin".
Plugins are not used in classic mode.
The plugin's settings are the resolver's options, described below: direction, groups and
qualifyAttributedClasses. A setting it does not know, or a value it cannot use, throws.
->withPlugins([JsonSchemaPlugin::class => ['direction' => 'output', 'groups' => ['read']]])
To add the resolver yourself, for example to place it elsewhere in the chain:
use OpenApi\Resolver; use OpenApi\Utils\TypedList; use Radebatz\OpenApi\JsonSchema\JsonSchemaResolver; $builder->withResolver(fn (Resolver $resolver) => $resolver->withResolvers( fn (TypedList $resolvers) => $resolvers->add(new JsonSchemaResolver()), ));
A class referenced by its name, for example new OA\Schema(ref: Order::class), is then
described like this:
- A class with attributes is described by its attributes. swagger-php's own resolver runs first, and this one only sees what that left unresolved.
- A class without attributes becomes a component with the schema Symfony generates.
- The classes it references are resolved the same way. Each is referenced by its name, so it gets its own component, from its attributes where it has them.
- swagger-php names the components. The names Symfony picks for its definitions are not used.
- PHP's own classes are not components. Symfony describes a
DateTimeImmutableinline where it is used. - A generic class is described once.
Envelope<string>andEnvelope<int>both reference the sameEnvelopecomponent, and a template type likeTallows any value.
Direction
By default one schema describes a class for both requests and responses, and marks a property
that can only be read or only be written with readOnly or writeOnly. To describe classes
for one direction only, set direction to input or output, or a Direction case:
JsonSchemaPlugin::class => ['direction' => 'output'] // or, adding the resolver yourself new JsonSchemaResolver(Symfony\Component\JsonSchema\Direction::Output);
Serializer and validator
symfony/json-schema uses the serializer and the validator when they are installed. Nothing needs
setting up here. With symfony/serializer, a schema describes what the serializer writes and reads:
- A
#[SerializedName]renames the property. - A
#[DiscriminatorMap]becomes ananyOfof the mapped classes, each requiring the type property.
To describe only the properties of some serializer groups, set groups:
JsonSchemaPlugin::class => ['groups' => ['read']] // or, adding the resolver yourself new JsonSchemaResolver(groups: ['read']);
The groups apply to every class in the document, unless a reference asks for others (see below). A property in none of them is left out, as the serializer leaves it out.
With symfony/validator installed, constraints become keywords: Length becomes minLength and
maxLength, Range becomes minimum and maximum, and NotBlank makes the property required.
A discriminator map gives a cycle for now. Each mapped class extends the base class, so
swagger-php adds the base class's component to the mapped class's allOf. The base class's
component is the anyOf of the mapped classes, so the two reference each other. This needs a
change in swagger-php.
Describing a class more than once
A reference can ask for its own groups or direction with the qualifiers of a Schema\Ref:
new OA\Schema\Ref(ref: Article::class, qualifiers: ['groups' => ['summary']])
The class is then described with these settings instead of the resolver's, as a component of its own, next to the one plain references use. Qualifiers left out fall back to the resolver's settings.
groupstakes a list of serializer groups, or one group as a string.directiontakesinputoroutput.
The classes a qualified class references keep its qualifiers, so Article with the summary
group references Author with the summary group too. swagger-php names each component after
its key, for example Article-groups.summary.
A referenced class with OpenAPI attributes is the exception. It is referenced plain and described
by its attributes, as anywhere else in the document. To give it the qualifiers too, and describe
it with the schema Symfony generates, set qualifyAttributedClasses:
JsonSchemaPlugin::class => ['qualifyAttributedClasses' => true] // or, adding the resolver yourself new JsonSchemaResolver(qualifyAttributedClasses: true);
A reference that puts qualifiers on a class with attributes itself is described by this resolver, since swagger-php's own resolver only handles plain references. A reference with any other qualifier is left to the next resolver.
License
The openapi-json-schema project is released under the MIT license.