packagist zircote/swagger-php 6.13.0

4 hours ago

A Rector set for moving to spec attributes

A new Rector set rewrites OpenApi\Attributes code to OpenApi\Spec. Pass it to Rector as --config vendor/zircote/swagger-php/rector/set/classic-to-spec.php. Renaming classes is not enough to migrate. Spec orders constructor parameters differently. Property, JsonContent and XmlContent no longer take schema keywords, which go in a nested schema: instead. So renamed code parses and then fails when the document is generated. The set first turns positional arguments into named ones, using the classic constructor's parameter names, and then renames the classes. It also lifts info:, servers:, tags: and externalDocs: off a root OpenApi into separate attributes, and moves schema keywords off Property, JsonContent and XmlContent into a nested schema:.

The set was run over two real codebases, one with 22 files of classic attributes and one with 354. Spec mode then generated the same document as classic for both. Migrating to spec attributes covers running it, the cases it leaves for you, and the code that drives the generator, which the set does not rewrite.

Spec and hybrid prune components, and now say so

In spec and hybrid mode, Cleanup removes every component that no path references, and it is on by default. It used to do this silently, so a schema kept only for client code generation disappeared from the document with no reason given. A run that removes anything now prints one notice on stderr, with the count and the setting that keeps them, cleanup.enabled=false.

That setting did not work in hybrid. -c went to the classic Generator, whose processors hybrid does not run, so every key except generator.* was silently ignored. Hybrid now sends generator.* to the Generator and every other key to the augmenters, so -c cleanup.enabled=false works. A hybrid key that no augmenter knows, such as -c operationId.hash=false, now prints an "Unknown config key" warning. It had no effect before either. See Behavioral differences.

Fixes in every mode

A $ref schema keeps its keywords

A property typed with a schema class compiles to a $ref, and keywords written on it, such as title or example, were dropped. Spec and hybrid kept only description. Classic writes a nullable $ref as a oneOf and kept everything on that oneOf. Beside a plain $ref in 3.1, it kept only description. title, default, example, examples, deprecated, readOnly and writeOnly are now kept in every mode, on the oneOf around a nullable $ref and beside a plain $ref in 3.1.

This changes 3.1 output for existing sources. A title or example declared beside a $ref now appears. In 3.0, the first of examples is written as example on the oneOf around a nullable $ref.

A list of scheme names is a security requirement

security: [['bearerAuth']], a list of scheme names instead of the [['bearerAuth' => []]] map, stopped hybrid and spec with a TypeError. Classic wrote it out as given, which is not a valid requirement. All three modes now read a bare scheme name as that scheme with no scopes.

Classic also wrote the optional requirement in a root security: [[]] as [] instead of {}. Root-level security now writes each requirement as an object, as operation-level security already did, so it comes out as [{}].

Spec and hybrid

A $ref is inferred only to a component

When a property's PHP type is a class, spec and hybrid added a $ref to it even when the class had no #[Schema]. The document then carried a class name that resolves to nothing, such as $ref: Brick\Math\BigInteger. The same inference replaced an explicit enum on a property typed with a backed enum, so the case values were lost. A $ref is now inferred only to a class that is a component, and never where enum is set. Classic, hybrid and spec now give the same output for both.

A class that fails while being resolved is skipped

Since 6.10, a class whose attributes fail to instantiate is skipped with a warning when the scan reaches it. Reached through a reference from another class, the same class ended the run with no document. Which way it was reached first depended on scan order. Both ways now skip the class with the same warning, and the run continues.

Spec pipeline

Folding two halves of one operation

By default, the later of two operations on the same path and method replaces the earlier one whole. When a route table contributed through withSpecification() and an attribute both describe an operation, the attribute has to repeat everything the route gave. Merge\Operations folds the two instead. It takes what only one half sets, and folds parameters by name and location and responses by status code. Merge\Mode decides who wins where both set the same field. A field both halves set to different values is logged as a warning.

It is opt-in, so nothing changes until you register it with Builder::withMergers(), ahead of the default Merge\LastWins, which claims every type: $mergers->insert(new Merge\Operations(), Merge\LastWins::class). A new Parameters augmenter, on by default, gives a ref parameter its component's name and location before the merge runs. That way a ref parameter and an inline one for the same name and location fold into one, and the reference is what survives. See Mergers.

For packages built on a Specification

Several things a package needed to copy from the augmenters are now public:

  • PathItemHierarchy::prefixFor() and pathFor() give the prefix an operation's #[PathItem] chain composes to, and the operation's full path. pathFor() reads the path as declared, so calling it after the PathItems augmenter has run adds the prefix twice.
  • PathItemHierarchy::parametersFor() lists the parameters an operation inherits from that chain, keyed by name and location. The nearer path item wins.
  • SourceLocation::qualifiedMethod() names the method a location is in as Class::method, using the class that declares it. Operation ids are built from it and come out unchanged.
  • ComponentIndex::parameterKey() gives a parameter's key, its name and location, before the build has resolved it. A parameter that is only a ref takes both from its component.

PathItems::resolvePrefix() is removed. It was protected, so a subclass that calls or overrides it has to move to prefixFor().

Documentation

Every hand-written page under guide/ and dev/, and every ADR, has been rewritten in plain sentences, following the new Voice section of Writing documentation.

Changes

  • docs(Related): list openapi-introspector by @DerManoMann in #2232
  • chore(deps-dev): bump @redocly/cli from 2.54.3 to 2.57.0 in the npm group across 1 directory by @dependabot[bot] in #2231
  • feat(Merge): add an opt-in Operations merger that folds two halves of one operation by @DerManoMann in #2233
  • fix(Resolver): skip a class that fails to collect while resolving, instead of ending the run by @DerManoMann in #2229
  • fix(Security): read a list of scheme names as a requirement with no scopes by @DerManoMann in #2226
  • feat(Cleanup): say when unreferenced components are removed, and let hybrid's -c turn it off by @DerManoMann in #2225
  • feat(Rector): a classic to spec migration set, and a guide for what it leaves by @DerManoMann in #2234
  • fix(Types): infer a $ref only to a component, and never over an explicit enum by @DerManoMann in #2228
  • docs(Related): remove openapi-extras, abandoned and archived by @DerManoMann in #2235
  • docs(Guide): rewrite the spec intro pages and README notes in plain sentences by @DerManoMann in #2237
  • docs(Guide): rewrite the spec attributes guide in plain sentences by @DerManoMann in #2238
  • docs(Guide): rewrite the extension points and migration guides in plain sentences by @DerManoMann in #2239
  • docs(Guide): take the em dashes and semicolons out of the remaining guide pages by @DerManoMann in #2240
  • docs(Dev): rewrite the contributor pages and ADRs in plain sentences by @DerManoMann in #2241
  • docs(Writing): add a voice section, and rewrite the page in it by @DerManoMann in #2236
  • fix(Schema): keep a $ref schema's annotations instead of only its description by @DerManoMann in #2227
  • feat(Specification): compose an operation's path prefix in PathItemHierarchy by @DerManoMann in #2242
  • feat(Utils): name the method a source location is in as Class::method by @DerManoMann in #2243
  • feat(Augmenter): give a ref parameter the name and location of its component by @DerManoMann in #2244
  • feat(Specification): list the parameters a PathItem chain declares for an operation by @DerManoMann in #2245

Full Changelog: 6.12.0...6.13.0

Don't miss a new swagger-php release

NewReleases is sending notifications on new releases.