From e4d7c32e1c3e57a189d449460e47fff9242f435e Mon Sep 17 00:00:00 2001 From: DerManoMann Date: Sat, 26 Sep 2026 17:30:11 +1200 Subject: [PATCH 1/2] docs(Roadmap): say when classic goes, and mark what the file says is marked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROADMAP.md said classic code is marked deprecated in v7 and that everything marked in v6 is removed in v7. Both halves were unenforced: src/ carried eight markers, none of them naming a version, and the docblock annotation surface the README has discouraged since 4.8 carried none at all. The file now states the rule it relies on — a marker names the version that removes the thing, and nothing is removed before the version its marker names — and the code is brought in line with it in the same change, because a plan that describes markers which do not exist is wrong from the moment it merges. The two judgement calls worth reviewing. Generator::UNDEFINED and isDefault() move from an open-ended marker to 8.0 rather than 7.0: every classic custom processor uses them, and at least one integration builds the annotation objects directly without ever scanning, so they go when classic goes. The docblock surface — DocBlockAnnotationFactory, DocBlockParser and the six alias/namespace methods on Generator, which nothing outside that surface calls — is marked for 8.0 for the same reason, and the relocation shims keep their existing 7.0 date. Each existing marker keeps the version it was actually added in, read off the history rather than dated to today. Parsing a docblock annotation also triggers a runtime deprecation, once per generator run rather than once per annotation, and only when an annotation is actually parsed — a project that has moved to attributes still has docblocks. symfony/deprecation-contracts was already required and called nowhere. --- ROADMAP.md | 86 ++++++++++++++++----- docs/guide/modes.md | 2 +- src/Analysers/DocBlockAnnotationFactory.php | 24 ++++++ src/Analysers/DocBlockParser.php | 3 + src/Analysers/TokenScanner.php | 2 +- src/Generator.php | 18 ++++- src/Pipeline.php | 2 +- src/SourceFinder.php | 2 +- src/Type/LegacyTypeResolver.php | 2 +- src/TypeResolverInterface.php | 2 +- src/Utils/TypeMapper.php | 2 +- 11 files changed, 116 insertions(+), 29 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index fd600eeeb..0db08d919 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,33 +1,79 @@ # Roadmap ## Overview -This is a high level roadmap predominantly for **v6**, **v7** and **v8**. +This is a high level roadmap for **v6**, **v7** and **v8** - what happens to the classic pipeline +and the new _Spec_ attributes and processing pipeline, and when. -In particular this is in relation to the introduction of the new _Spec_ attributes and processing pipeline and what may/will happen when. +The discussion and the tick list of what has shipped are in +[#1953](https://github.com/zircote/swagger-php/issues/1953). This file is the plan; the issue +tracks it. + +## Deprecation rule +A `@deprecated` marker names the version that removes the thing: + +```php +/** @deprecated since 6.11, removed in 8.0 - use ... instead */ +``` + +Nothing is removed before the version its marker names, and nothing is removed without a marker. +Something marked for `8.0` keeps working through **v7** unchanged. That is what "no cliff" means +here: the marker is the promise. ## Timeline ### v6 -`6.5.0` saw the introduction of the new `Spec` system. Right now this is considered not yet production ready. -The remainder of **v6** will be defined by improving and completing the new system. +`6.5.0` introduced the `Spec` system as opt-in beta. The remainder of **v6** is about completing +it and getting people onto it: + +* `Spec` attributes declared frozen, and the **beta** label dropped in that minor +* 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 +* 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 +* the relocation shims already marked (`Pipeline`, `SourceFinder`, `Analysers\TokenScanner`, + `Utils\TypeMapper`, `LegacyTypeResolver`) are removed in `7.0` +* `Generator::UNDEFINED` and `Generator::isDefault()` stay until `8.0` - every classic custom + processor uses them, so they go with classic and not before + +Classic attributes, the `Generator` and the processors are **not** marked in v6. The replacement +is still beta for part of the major, and a stable API is not deprecated in favour of a beta one. ### v7 -This is where things will start to turn: -* Default mode will switch to `hybrid` - this means existing `classic` projects should keep working via the bridge (probably with the exception of `NelmioApiDocBundle`, as that relies on a lot of actual `classic` features), although they will be routed through the new `spec` pipeline via a custom bridge. -* All classic code - annotations + attributes and related pipeline code will be marked `deprecated` -* All code marked `depecated` in **v6** will be removed -* Bridge is marked `deprecated` -* `Builder::setMode()` is marked `deprecated` -* `nikic/php-parser` is raised to `^5.0` — the `^4.19` branch parses no further than +This is where things turn: +* default mode switches to `hybrid` - existing `classic` projects keep working, routed through + the new pipeline via the bridge (probably with the exception of `NelmioApiDocBundle`, as that + relies on a lot of actual `classic` features) +* all classic code - annotations, attributes and the related pipeline code - is marked + `@deprecated`, removed in `8.0` +* the bridge and `Builder::setMode()` are marked `@deprecated`, removed in `8.0` +* everything whose marker says `7.0` is removed +* `nikic/php-parser` is raised to `^5.0` - the `^4.19` branch parses no further than PHP 8.3 syntax -## v8 -`classic` is removed from the codebase, leaving only the spec pipeline. Annotations are no longer supported at all. - -**Finding what to remove.** Code carries `@deprecated` from **v7**, which is the primary -marker and the one tooling understands. Documentation, fixture layout and test-matrix -structure cannot carry it — nothing consumes a docblock in a markdown file or a yaml -expectation — so those are marked inline with the mode they exist to serve: `[classic]`, -`[hybrid]`, or `[classic/hybrid]` for what serves both. `grep -rnE '\[(classic|hybrid)'` -lists them, and all of it goes when `classic` does. +* the v7 migration guide states the v8 namespace move below, so nobody meets it as a surprise + +Open: whether the default **output** version follows the default mode. `classic` defaults to +OpenAPI 3.0.0 and `hybrid` to 3.1.0 today, so switching the default mode also switches the +default output version for everyone unless v7 pins 3.0.0. Decided in #1953 before v7. + +### v8 +`classic` is removed from the codebase, leaving only the spec pipeline. Annotations are no longer +supported at all. + +The spec attributes move from `OpenApi\Spec` to `OpenApi\Attributes`. With one pipeline left, +"Spec" is a mode name that means nothing, and `OpenApi\Attributes` is the better home. For code +written against `OpenApi\Spec` that is one `use` line per file, and the rector set does it. + +Open: whether `OpenApi\Spec` stays as deprecated aliases for one major, so the move is optional +until v9. + +## Finding what to remove +Code carries `@deprecated` with the version that removes it, which is the primary marker and +the one tooling understands. Documentation, fixture layout and test-matrix structure cannot +carry it - nothing consumes a docblock in a markdown file or a yaml expectation - so those are +marked inline with the mode they exist to serve: `[classic]`, `[hybrid]`, or `[classic/hybrid]` +for what serves both. `grep -rnE '\[(classic|hybrid)'` lists them, and all of it goes when +`classic` does. The marker names the mode rather than the release on purpose. A thing is `[hybrid]` for as long as hybrid exists, which stays true whatever happens to this timeline; `[v8: drop]` would diff --git a/docs/guide/modes.md b/docs/guide/modes.md index 3b0836c30..4f704ebca 100644 --- a/docs/guide/modes.md +++ b/docs/guide/modes.md @@ -124,5 +124,5 @@ The recommended migration path is: ::: 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 code might move to `OpenApi\Attributes`. +- **v8** — classic removed. `setMode()` removed. Spec becomes default. Spec attributes move to `OpenApi\Attributes`, one `use` line per file. ::: diff --git a/src/Analysers/DocBlockAnnotationFactory.php b/src/Analysers/DocBlockAnnotationFactory.php index a5b177654..347413f53 100644 --- a/src/Analysers/DocBlockAnnotationFactory.php +++ b/src/Analysers/DocBlockAnnotationFactory.php @@ -11,12 +11,20 @@ use OpenApi\Generator; use OpenApi\GeneratorAwareTrait; +/** + * Builds annotations from docblock comments. + * + * @deprecated since 6.11, removed in 8.0 - use attributes instead + */ class DocBlockAnnotationFactory implements AnnotationFactoryInterface { use GeneratorAwareTrait; protected ?DocBlockParser $docBlockParser = null; + /** Reset per generator run, so a scan of a thousand annotated files reports once. */ + protected bool $deprecationReported = false; + public function __construct(?DocBlockParser $docBlockParser = null) { $this->docBlockParser = $docBlockParser ?: new DocBlockParser(); @@ -30,6 +38,7 @@ public function isSupported(): bool public function setGenerator(Generator $generator): static { $this->generator = $generator; + $this->deprecationReported = false; $this->docBlockParser->setAliases($generator->getAliases()); @@ -60,6 +69,7 @@ public function build(\Reflector $reflector, Context $context): array $annotations = []; foreach ($this->docBlockParser->fromComment($comment, $context) as $instance) { if ($instance instanceof OA\AbstractAnnotation) { + $this->reportDeprecation(); $annotations[] = $instance; } else { if ($context->is('other') === false) { @@ -74,4 +84,18 @@ public function build(\Reflector $reflector, Context $context): array return []; } + + /** + * Reported on the first docblock annotation actually parsed, not on the first docblock + * seen: a project that has migrated to attributes still has docblocks everywhere. + */ + protected function reportDeprecation(): void + { + if ($this->deprecationReported) { + return; + } + + $this->deprecationReported = true; + trigger_deprecation('zircote/swagger-php', '6.11', 'Docblock annotations are deprecated and will be removed in 8.0; use attributes instead'); + } } diff --git a/src/Analysers/DocBlockParser.php b/src/Analysers/DocBlockParser.php index 015cb4dbd..5b84cacb7 100644 --- a/src/Analysers/DocBlockParser.php +++ b/src/Analysers/DocBlockParser.php @@ -14,6 +14,9 @@ /** * Extract swagger-php annotations from a [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) using Doctrine's DocParser. */ +/** + * @deprecated since 6.11, removed in 8.0 - use attributes instead + */ class DocBlockParser { protected DocParser $docParser; diff --git a/src/Analysers/TokenScanner.php b/src/Analysers/TokenScanner.php index c93b408a1..c5ab964b9 100644 --- a/src/Analysers/TokenScanner.php +++ b/src/Analysers/TokenScanner.php @@ -7,7 +7,7 @@ namespace OpenApi\Analysers; /** - * @deprecated use {@see \OpenApi\Utils\TokenScanner} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see \OpenApi\Utils\TokenScanner} instead */ class TokenScanner extends \OpenApi\Utils\TokenScanner { diff --git a/src/Generator.php b/src/Generator.php index 5280f67f6..d0c47c1f3 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -26,7 +26,7 @@ */ class Generator { - /** @deprecated Use {@see Undefined::UNDEFINED} instead. */ + /** @deprecated since 6.3.1, removed in 8.0 - use {@see Undefined::UNDEFINED} instead */ public const UNDEFINED = Undefined::UNDEFINED; /** @var array */ @@ -77,7 +77,7 @@ public function __construct(?LoggerInterface $logger = null) } /** - * @deprecated use {@see Undefined::isDefault()} instead + * @deprecated since 6.3.1, removed in 8.0 - use {@see Undefined::isDefault()} instead * * @param mixed ...$value */ @@ -88,12 +88,17 @@ public static function isDefault(...$value): bool /** * @return array + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function getAliases(): array { return $this->aliases; } + /** + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function addAlias(string $alias, string $namespace): Generator { $this->aliases[$alias] = $namespace; @@ -103,6 +108,8 @@ public function addAlias(string $alias, string $namespace): Generator /** * @param array $aliases + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function setAliases(array $aliases): Generator { @@ -113,12 +120,17 @@ public function setAliases(array $aliases): Generator /** * @return list|null + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function getNamespaces(): ?array { return $this->namespaces; } + /** + * @deprecated since 6.11, removed in 8.0 - docblock annotations only + */ public function addNamespace(string $namespace): Generator { $namespaces = (array) $this->getNamespaces(); @@ -129,6 +141,8 @@ public function addNamespace(string $namespace): Generator /** * @param list|null $namespaces + * + * @deprecated since 6.11, removed in 8.0 - docblock annotations only */ public function setNamespaces(?array $namespaces): Generator { diff --git a/src/Pipeline.php b/src/Pipeline.php index fb0c9d77c..303c379c6 100644 --- a/src/Pipeline.php +++ b/src/Pipeline.php @@ -7,7 +7,7 @@ namespace OpenApi; /** - * @deprecated use {@see Utils\Pipeline} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see Utils\Pipeline} instead * * @extends Utils\Pipeline */ diff --git a/src/SourceFinder.php b/src/SourceFinder.php index ac1a93e25..d46c53f7f 100644 --- a/src/SourceFinder.php +++ b/src/SourceFinder.php @@ -3,7 +3,7 @@ namespace OpenApi; /** - * @deprecated use {@see Utils\SourceFinder} instead + * @deprecated since 6.3.1, removed in 7.0 - use {@see Utils\SourceFinder} instead */ class SourceFinder extends Utils\SourceFinder { diff --git a/src/Type/LegacyTypeResolver.php b/src/Type/LegacyTypeResolver.php index 2d55cad68..a66d09c2b 100644 --- a/src/Type/LegacyTypeResolver.php +++ b/src/Type/LegacyTypeResolver.php @@ -12,7 +12,7 @@ use OpenApi\Undefined; /** - * @deprecated use `TypeInfoTypeResolver` instead + * @deprecated since 6.0.0, removed in 7.0 - use `TypeInfoTypeResolver` instead */ class LegacyTypeResolver extends AbstractTypeResolver { diff --git a/src/TypeResolverInterface.php b/src/TypeResolverInterface.php index 0bdfc02de..41b426850 100644 --- a/src/TypeResolverInterface.php +++ b/src/TypeResolverInterface.php @@ -11,7 +11,7 @@ interface TypeResolverInterface { - /** @deprecated Use TypeMapper::NATIVE_TYPE_MAP instead */ + /** @deprecated since 5.5.2, removed in 7.0 - use {@see TypeMapper::NATIVE_TYPE_MAP} instead */ public const NATIVE_TYPE_MAP = TypeMapper::NATIVE_TYPE_MAP; /** diff --git a/src/Utils/TypeMapper.php b/src/Utils/TypeMapper.php index 254169480..dbf23ef75 100644 --- a/src/Utils/TypeMapper.php +++ b/src/Utils/TypeMapper.php @@ -9,7 +9,7 @@ use OpenApi\Type; /** - * @deprecated use {@see Type\TypeMapper} instead + * @deprecated since 6.9.0, removed in 7.0 - use {@see Type\TypeMapper} instead */ class TypeMapper extends Type\TypeMapper { From edaeaeb379af32457cf2f1534d9ce47087646a79 Mon Sep 17 00:00:00 2001 From: DerManoMann Date: Sat, 26 Sep 2026 17:45:33 +1200 Subject: [PATCH 2/2] docs(Roadmap): name every surface mode selection takes with it The v7 list said `Builder::setMode()` goes and stopped there. Three more surfaces die with it and nothing said so: the `Builder\Mode` enum, `Console\GenerateInput::$mode`, and the CLI's `-m`/`--mode` option. Also worth stating because it makes these unlike every other marker in the file: they name no replacement. There is one pipeline in 8.0, so there is nothing to select rather than a different way to select it. --- ROADMAP.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 0db08d919..7a43003e5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -46,7 +46,10 @@ This is where things turn: relies on a lot of actual `classic` features) * all classic code - annotations, attributes and the related pipeline code - is marked `@deprecated`, removed in `8.0` -* the bridge and `Builder::setMode()` are marked `@deprecated`, removed in `8.0` +* the bridge and mode selection are marked `@deprecated`, removed in `8.0` - that is + `Builder::setMode()`, the `Builder\Mode` enum, `Console\GenerateInput::$mode` and the CLI's + `-m`/`--mode` option, which go together because there is one pipeline in `8.0` and nothing + left to select. These markers name no replacement, unlike every other marker here * everything whose marker says `7.0` is removed * `nikic/php-parser` is raised to `^5.0` - the `^4.19` branch parses no further than PHP 8.3 syntax