Skip to content

EE-283: Add Symfony 7.4 support - #19

Draft
vinayak-iyer-paysera wants to merge 43 commits into
paysera:masterfrom
vinayak-iyer-paysera:EE-283-symfony-7-support
Draft

vinayak-iyer-paysera wants to merge 43 commits into
paysera:masterfrom
vinayak-iyer-paysera:EE-283-symfony-7-support

Conversation

@vinayak-iyer-paysera

@vinayak-iyer-paysera vinayak-iyer-paysera commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Opens as a draft: it needs the Symfony 7 releases of paysera/lib-dependency-injection (paysera/lib-dependency-injection#8),
paysera/lib-pagination (paysera/lib-pagination#18) and paysera/lib-normalization-bundle (paysera/lib-normalization-bundle#10) first; their current releases stop at Symfony 6.

Summary

I make the bundle install and run on Symfony 7.4 and keep Symfony 3.4 to 6.4 working as they do. I propose releasing it as
1.9.0.

Why

  • Every Symfony constraint stops at ^6.0, and Symfony 7 requires getConfigTreeBuilder(): TreeBuilder.
  • Seven components the code uses are not in composer.json, so a PHP 8.3 install can mix routing, http-foundation and
    security-core 7.4 into a 6.4 install; 144 of the 259 tests then fail.
  • Symfony 7.1 rewrote Request::getPreferredLanguage(): the listener's "default" placeholder now wins for German ("de").
  • Symfony 7 gives the route loader no annotation reader, so the bundle's docblock options would be dropped silently: a
    dropped @RequiredPermissions leaves an endpoint without its permission check.

Changes

  • composer.json: ^7.4 on framework-bundle, security-bundle, validator and dev yaml; config, dependency-injection,
    http-foundation, http-kernel, property-access, routing, security-core required at the same constraint; psr/log
    ^3.0 allowed; dev doctrine/orm floor ^2.6.3 (2.6.0 fails on PHP 7.3+).
  • getConfigTreeBuilder(): TreeBuilder, PayseraApiExtension::load(): void, ?Type $x = null on 11 parameters.
  • LocaleListener reads Accept-Language itself and applies the Symfony 3.4 to 7.0 rule on every line: the first entry,
    in the client's order, that is a configured locale; a regional tag (de_CH) also offers its primary language (de)
    unless the header lists it.
  • On Symfony 7, a route whose action or class docblock names one of the bundle's seven annotations (short, through an
    alias or in full) fails to load with a ConfigurationException pointing to the attribute of the same name. It matches
    names only. Symfony 4.4 to 6.4 are unchanged.
  • The test application runs on every Symfony line (on Symfony 7 from sf7_common.yml and attribute twins of the docblock
    controllers); tests for the locale rule and the Symfony 7 refusal.
  • Workflow: Symfony 7.* on PHP 8.2 and 8.3 (EE-283: Update actions/checkout to v7 #20, merged, moved it to actions/checkout v7), and, after the second
    review, every job also with lowest dependencies, as asked on lib-dependency-injection#8 (38 jobs: 24 highest, 14 lowest).
    The 10 excluded lowest combinations fail inside vendor/: Symfony 4's routing annotation loader on PHP 8,
    lib-object-wrapper 0.3.0 on PHP 8.1 and later, and DoctrineBundle's lowest release with Symfony 6.0.
  • CHANGELOG.md 1.9.0 and a README note.

Backward compatibility

A minor: nothing is removed, PHP 7.1+ and Symfony 3.4+ stay allowed. A check from 1.8.2 lists 2 changes, the two return types;
they break only a subclass overriding either method without the type, as the CHANGELOG says. One behaviour change on every
line: Accept-Language: default, de now gives de (default was the listener's placeholder).

Test plan

  • The suite, 277 tests, locally with the workflow's own commands in 8 jobs, from PHP 7.1 (Symfony 3.4, lowest) to 8.3
    (Symfony 7.4, with the three dependency candidates): all pass. src/ line coverage 85.9 % on Symfony 6.4 (master:
    47.2 %).
  • The Symfony 7 refusal test fails before the check exists and passes with it; the Symfony 7 functional tests load
    every attribute route without a refusal.
  • The 24 lowest combinations: 14 pass and stay in the matrix; the 10 that fail are excluded (see Changes).
  • A Symfony 6.4 production application's full suite: 2,195 tests, the same result for each before and after (run before
    the Symfony 7 check was simplified; that code does not run on Symfony 6.4).
  • CI on this pull request: 34 of 38 jobs pass (https://github.com/paysera/lib-api-bundle/actions/runs/36421260202). The four Symfony 7 jobs (two highest, two lowest) fail at install, because
    the released versions of the three dependencies stop at Symfony 6; they pass once those releases are out.

Known gap

  • testAnItemOfOnlyASemicolonIsNotALanguage is skipped wherever Symfony's own parser throws on the header ;. Today only
    http-foundation 4.4 does that, and 1.8.2 throws there as well. If a later http-foundation release broke on ;, the test
    would be skipped instead of failing.

🤖 Generated with Claude Code

vinayak-iyer-paysera and others added 30 commits September 23, 2026 20:43
…le uses

framework-bundle, security-bundle and validator (and yaml in require-dev) now allow ^7.4.

The code also uses config, dependency-injection, http-foundation, http-kernel, routing,
security-core and property-access directly, but composer.json did not name them. They arrived
through other packages with no limit, so on PHP 8.3 an install next to the 6.4 bundles took
routing, http-foundation and security-core 7.4, and 144 of the 259 tests failed. They are now
required at the same constraint as the bundles.

psr/log also allows ^3.0. The doctrine/orm dev floor moves to 2.6.3: with the lowest
dependencies on PHP 7.4, 2.6.0 failed 140 tests with "continue targeting switch is
equivalent to break".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Symfony axis gains 7.*, excluded on PHP 7.1 to 8.1 because Symfony 7 needs PHP 8.2, so
PHP 8.2 and 8.3 each get one Symfony 7 job with the highest dependencies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Symfony 7 reads no Doctrine annotations and dropped two settings the test application used,
so on 7.4 the suite could not boot. Each line now gets what it accepts:

- The fixture entities are mapped in Doctrine XML (one mapping for every Doctrine version)
  instead of @Orm docblocks.
- The routes defined in XML move to explicit_routing.xml. Symfony 7 imports that file and the
  attribute controllers only (sf7_routing.yml); the two docblock-routed controllers that other
  tests rely on get attribute twins with the same paths. The docblock half of
  FunctionalAnnotationsTest is skipped on Symfony 7.
- The Symfony 7 security settings omit enable_authenticator_manager (removed in 7.0).
- The fixture bundle's Configuration declares its TreeBuilder return type.
- HttpKernelHelper looked for the constant "…HttpKernelInterfaceMAIN_REQUEST" (no "::"), so it
  always fell back to MASTER_REQUEST, which Symfony 7 removed.
- EntityValidatorTest no longer marks three cases as asserting nothing: they expect the
  validator to be called once. With PHPUnit 7.5 and Mockery 1.3 those cases were reported as
  risky because Mockery counted its expectations as assertions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Configuration::getConfigTreeBuilder() now returns TreeBuilder: Symfony 7's
ConfigurationInterface declares that type, and without it the bundle cannot be loaded on
Symfony 7 (fatal "must be compatible"). PayseraApiExtension::load() now returns void, which
Symfony 8 requires; Symfony 7.4 only reports the missing type as a deprecation.

A subclass that overrides either method without the type stops compiling. No such subclass
exists in the applications that use the bundle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The locale listener asked Request::getPreferredLanguage() to choose between a placeholder
"default" and the configured locales, and treated "default" as no match. Symfony 7.1 rewrote
that method to match by prefix, so "default" now wins whenever a request asks for German
("default" starts with "de"), and it also ranks the languages differently (en-US now matches
"en" before a de-CH;q=0.9 matches "de").

The listener now matches the header itself with the rule Symfony 3.4 to 7.0 apply: the first
header language, in the client's order of preference, that is a configured locale, where a
regional variant also offers its primary language unless the header lists it. The results are
unchanged below Symfony 7.1. The existing eight test cases stay as they were; five cases are
added, three of which fail on Symfony 7.4 with the old listener.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Symfony 7 gives the route loader no Doctrine annotation reader, so on Symfony 7 a controller
routed with #[Route] but configured with the bundle's docblock annotations (@Body, @query,
@PathAttribute, @ResponseNormalization, @RequiredPermissions, @Validation, @BodyContentType)
was loaded with none of those options, without any error: an endpoint protected by
@RequiredPermissions would have answered everyone.

Where the route loader has no annotation reader (Symfony 7 and later), loading such a route
now fails with a ConfigurationException that names the controller method, the annotations it
uses and the attributes to use instead (the #[...] attributes exist since 1.8.0). The
annotations are found without an annotation reader: DocblockAnnotationFinder resolves each
docblock tag through the use imports of the file that declares it. Symfony 4.4 to 6.4 are
unchanged: the reader still applies the annotations there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Eleven parameters in nine files were written "Type $x = null", which PHP 8.4 reports as a
deprecated implicit nullable type. They are now "?Type $x = null": the same type on every
PHP version the bundle supports (7.1 and later), so no caller or subclass is affected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The attribute's first parameter is the options array, so a positional list made the test
fixture fail with a TypeError on Symfony 7.4. The bundle's own fixtures and the one
application using the attribute pass it as permissions: [...].

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tests only. The suite executed 910 of the 1,027 executable lines in src/ (88.6 %); the largest
gap was ErrorBuilder, which builds every error response and had no test (30 of 84 lines).

- ErrorBuilderTest: the error code, status and message for each kind of exception the builder
  maps, with the codes the bundle configures, and the API exception's details.
- The guards that were never reached: reading an option before it is set, an unknown path
  attribute resolver type, validation without the validator service, a path attribute or query
  whose type cannot be guessed, explicit arguments winning over the signature.
- RestResponseListener on a request that is not a REST request and on a controller returning
  nothing; RestRequestHelper for a controller with no identifier.
- Two request-body errors in the functional suite: a body without Content-Type where the
  endpoint restricts content types, and a JSON body that does not decode.
- RoutingAttributeLoader on Symfony 6.4 when the application disabled annotations: the
  docblock options stay ignored there, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…test

QueryResolverOptions starts with validation options set, so the guard is only reached once they
are removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only internal classes have no file, and they carry no docblocks, so readImports() never met
one; the guard was a line no test could reach. The file name check stays, inside the expression.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…der found them

The Symfony 7 guard missed five forms that Doctrine's reader applies on Symfony 4.4 to 6.4, so
on Symfony 7 such a route still loaded with its options silently dropped: a group import
(use ...\Annotation\{Body, RequiredPermissions}), a comma-separated import, an import on the
namespace line, a full class name without the leading backslash, and an action that comes from
a trait whose file imports the annotation. The finder now reads the use statements in all
these forms, merges the imports of the file that declares the method with the declaring
class's (as Doctrine does for traits), and resolves a name through an import, then the
namespace, then as a fully qualified name.

It also counts an @ only where Doctrine's lexer counts a top-level annotation (at the start of
the docblock text, after whitespace or *), so {@inheritdoc}-style inline tags, e-mail
addresses and annotations nested in another annotation's arguments are no longer reported.
Each file's imports are read once per route loading.

The error message no longer claims the endpoint would run without options when an attribute
is also present, writes the attribute class with its leading backslash, and names the
attribute interface for an application's own annotation class instead of an attribute that
does not exist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The locale listener still took the header's languages from Request::getLanguages(). Symfony 7.1
rewrote that method too (it lowercases and reformats the tags), so on 7.1 and later a client
sending "DE" got German where every earlier line kept the default, and "EN,de" got English
instead of German. On http-foundation releases that build that list from the header's array keys
(3.4, 4.4.0 to 4.4.45, 5.4.0 to 5.4.12, 6.0.0 to 6.0.12, 6.1.0 to 6.1.4) a numeric tag came back as
an int and the listener's strpos() threw a TypeError under strict types, where the old listener
returned a locale.

The listener now reads the header through AcceptHeader, whose order is the same on every line,
and writes the tags the way getLanguages() did up to 7.0 ("de-CH" becomes "de_CH", a tag without
a region keeps its case). Five cases are added: "DE" and "EN,de" fail on Symfony 7.4 with the
previous listener, "1" and "de, 1" throw on http-foundation 4.4.0 with it, and "i-cherokee" runs
the i- branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…supports

The test application picked its Symfony 7 configuration by the http-kernel major, while the
bundle decides by what symfony/routing supports. Composer can install routing 7.4 next to
framework-bundle 6.4 (framework-bundle 6.4 allows it), and there the application kept importing
its controllers with the "annotation" route type that routing 7 no longer loads: 144 tests failed,
on master as well. It now asks routing whether its attribute loader still has an annotation reader,
the question the bundle's own loader asks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…test setup

framework-bundle 6.4 installs next to validator 7.4, and it still configures the Email
validator with the "loose" mode validator 7 removed, so every request validating an e-mail
failed with "The defaultMode parameter value is not valid". The Symfony 7 test configuration now
names html5, validator 7's own default: the five validation tests pass on that mix, and nothing
changes on a pure Symfony 7 install.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ach their guards

- ErrorBuilderTest builds paysera_api.error_builder from the bundle's own services.xml instead of
  repeating its eight configureError() calls, so a change to the configured error codes shows up.
- The duplicate-annotation test passed on an earlier exception (its first annotation could not
  guess a type) and never reached the guard it is named after; both annotations now name their
  type, and the test expects the guard's message.
- Element types on the new data providers, an imported RuntimeException, and the eight
  implicitly nullable test parameters PHP 8.4 reports written ?Type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… found them

The Symfony 7 check still missed annotations that Doctrine's reader applies on Symfony 4.4 to
6.4, so those routes loaded on Symfony 7 with their options dropped, and it reported text that
Doctrine never read as an annotation.

- Imports: the use statements are read with the PHP tokenizer, as Doctrine's TokenParser does,
  from the class's file up to the class and from the class's namespace declaration on. Comments
  inside use statements, several statements on one line and braced or repeated namespace blocks
  now count as Doctrine counts them; a commented-out import, a trait used in the class body and
  another namespace's import no longer do. A method from a trait also gets the imports of the
  trait's file, as in Doctrine's getMethodImports().
- Docblocks: reading starts at the first "@" after a space, a tab or "*"; after that an
  annotation starts at an "@" after whitespace (a no-break space too) or "*"; a quoted string is
  text; a name followed by "-" is not an annotation; the arguments of an annotation are skipped,
  so an annotation nested in them is not reported as the method's own.

On 19 sample controllers the check now reports what Doctrine's AnnotationReader reports, with
doctrine/lexer 2 and 3; with lexer 1 the no-break space case differs, because lexer 1 does not
read it as whitespace. On 207 controller files of Paysera applications (688 docblocks) the
imports and the annotations found are the same as Doctrine's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nguage item

http-foundation 3.4 gives null for an item that is only ";" and false for a lone quote, and the
listener passed that to strpos() under strict types: "Accept-Language: '" ended in a TypeError
where 1.8.2 kept the locale. The value is read as a string again, as it was before the listener
read the header itself. Two test cases cover a malformed item alone and next to a language.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ErrorBuilderTest covered six of the eight codes in services.xml. It now also checks
invalid_parameters (with its configured message), invalid_state and not_acceptable, so a change
to any configured status or message fails a test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… across Symfony lines

The comments said the listener picks the same locale on every Symfony line. It applies the same
rule and tag format on every line; for some malformed headers the releases' own parsing of
Accept-Language still differs (for example "de;q=, en"), as it did before this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s reader

Six rules of the check ran in the suite without a test that fails when they break, or did not
run at all. Each new case gives the result Doctrine's AnnotationReader gives (checked with
doctrine/lexer 2 and 3), and each fails when its rule is taken out:

- a `use function` or `use const` import with an annotation's short name does not replace the
  class import (the statement ends at `function` / `const`);
- a method a class declares over its trait's method resolves through the class file's imports
  only, not the trait file's;
- the parentheses after a class that is not an annotation are read, so an annotation in them
  counts, as in Doctrine;
- reading starts at the first "@" after a space, a tab or "*": an annotation at the start of a
  line before it is skipped, and one after a tab starts the reading;
- a class declared in evaluated code, such as a test double, has no file and gives no imports.

The no-break space fixture moves into WhitespaceBeforeAnnotationController with the tab case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…keeps a name

The quoted-string pattern was written in two regular expressions; it is now one constant. The
class comment said a name followed by "-" is never an annotation, but a "-" that starts a
number keeps it, in Doctrine and in the code. A comment line in the locale listener is rewrapped
to the line length. No behaviour changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On http-foundation 3.4 such an item gives null, which the listener must read as no language; the
existing lone-quote cases cover only false. http-foundation 4.4 fails on an empty item itself,
as with 1.8.2, so the test is skipped where Symfony's own parser throws.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n it

On http-foundation 4.4 the parser reads a value that is not there before it throws its
TypeError, and PHP 7.4 reports that as a notice, PHP 8 as a warning. PHPUnit turns either into
an error first, so the test errored on Symfony 4.4 with PHP 7.4 and later instead of being
skipped. It now skips on any failure of Symfony's own parser.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he rest of its rules

Two inputs made the Symfony 7 check miss an annotation that Doctrine's reader applies on
Symfony 4.4 to 6.4:

- `@Target( @RequiredPermissions(...) )` where `Target` is an application's annotation class
  found through the namespace: Doctrine ignores tag names such as Target and Required unless
  they are imported or written in full, and then reads what the parentheses hold as top-level
  annotations. The check now skips an annotation's arguments only when its name was imported or
  written in full, where Doctrine always reads them.
- an "@" right after a quote (`see "the notes"@RequiredPermissions(...)`): Doctrine's lexer
  measures a string without its quotes, so the "@" is not glued to it.

New cases pin rules no test protected before, each checked against Doctrine's reader and each
failing when its rule is taken out: an "@" right after a star, a one-letter alias, aliases in a
comma list, an import written with a leading backslash, an alias two namespace levels up, an
import on the class's own line, a docblock comment inside an import, arguments after a space,
a parenthesis inside a quoted argument, an annotation without arguments before another, a ":"
or a negative number after a name, a class docblock resolved in its own namespace and file,
two classes in one file with their own namespace blocks, and a trait whose import wins over the
class's for the trait's method.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ErrorBuilder falls back to a hard-coded 500 for other exceptions, so no test read the status
configured for internal_server_error. A data set with that code now does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guard skipped on any failure of Symfony's parser, so a later release that broke on ";" would
have skipped the test instead of failing it. Only http-foundation 4.4 fails on an empty item
(3.4, 5.4, 6.4 and 7.4 parse it), so the test now skips on Symfony 4 and runs everywhere else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lves them

Four more inputs where the Symfony 7 check and Doctrine's reader (Symfony 4.4 to 6.4) differed:

- a name written on both sides of a "\" with whitespace, a line break or "*" between them
  (`@REST\ RequiredPermissions`): Doctrine joins the parts, so the check now does too;
- a name with two leading backslashes: Doctrine strips them all;
- a bundle annotation written in another case after an alias (`@REST\requiredPermissions`):
  Doctrine found it once an earlier route had loaded the class, but on Symfony 7 nothing may have
  loaded it, and the autoloader looks for a file in the written case. The check now loads the
  bundle's own annotation classes before resolving names;
- a name with a namespace that is not imported (`@Paysera\...\Query(... @Validation ...)`):
  Doctrine never ignores such a name, so its arguments are skipped again, as for imported names.

The first three made the check miss an annotation Doctrine applies; the fourth made it report a
nested one. New cases also pin that reading does not start at an "@" glued to a quote, and that
a class docblock resolves in the route class's own namespace when the method is inherited. Each
case gives Doctrine's result and fails when its rule is taken out. The class comment now says
which arguments are skipped and in which direction the check can still differ from Doctrine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…of the same name

Doctrine tries the controller's namespace before the global name, and so does the check; no test
held that order. An application annotation named Directory, like PHP's own \Directory class,
now pins it: tried in the other order, the check finds PHP's class and drops the annotation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…joining rules

7bf10ab skipped the arguments of every name with a namespace, on the grounds that Doctrine
never ignores one. It does when a class lists the name with @IgnoreAnnotation, and then reads
what the parentheses hold as top-level annotations, so the check missed an annotation Doctrine
applies. Arguments are again skipped only after an imported name or one written in full, where
Doctrine reads them in every case; after any other name the check reads on and can only report
more. The bundle's annotation classes are listed with scandir() instead of glob(), which reads
brackets in the install path as a pattern.

New cases pin a name joined across two separators, a no-break space after a separator, a name
that does not take the next line's words, three leading backslashes, and a name the class tells
Doctrine to ignore. Each gives Doctrine's result and fails when its rule is taken out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sion

The skip checked http-kernel's major version, but the failure is http-foundation's, and the two
can be installed at different majors. The test now skips exactly when Symfony's own parser fails
on ";" (http-foundation 4.4, as with 1.8.2), and runs everywhere else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The lowest supported doctrine/lexer, 1.0, differs from 1.2 and later in two ways. It does not read a no-break space
as whitespace, so a parenthesis after one is not an annotation's arguments. It reads a docblock that is not valid
UTF-8 byte by byte, where the later versions read no annotations in it at all.

The finder followed the later versions only. On lexer 1.0 it skipped an annotation Doctrine applied (the one inside a
parenthesis after a no-break space), and a route would have loaded without it. It now takes only ASCII whitespace
before arguments and reads a docblock that is not UTF-8 byte by byte: on every lexer version, the reading that
reports more.

The class comment also states the ignored-name rule as it is: Doctrine passes over such a name unless it names an
annotation class through an import or in full.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… its path

The finder lists the bundle's Annotation directory with scandir rather than glob, because glob reads "[" and "]" in
the path as a pattern and would then find nothing. A test now copies the bundle's source under a directory named with
brackets and checks that a name written in another case still resolves from there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Repeated tests become one test with a data provider, several getter checks
become one assertion per case, and the pull request adds no comments; the
reasons they gave are in the pull request description.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vinayak-iyer-paysera

Copy link
Copy Markdown
Contributor Author

Following your note on lib-dependency-injection#8, every job here also runs with lowest dependencies where that combination can run (a11829b): 38 jobs, the 24 highest plus 14 lowest. I ran all 24 lowest combinations locally with the workflow's own commands. 14 pass with 389 tests each. The other 10 fail inside vendor/, because the oldest releases the constraints allow can't run together or on PHP 8, so I excluded them with 8 entries:

  • PHP 8.0–8.3 with Symfony 4: Symfony 4's old routing annotation loader misreads the fixture controllers' class names on PHP 8 (Class "1\AnnotatedClassRequiredPermissionsController" does not exist).
  • PHP 8.1–8.3 with Symfony 5: paysera/lib-normalization 1.2.0 installs paysera/lib-object-wrapper 0.3.0, whose ArrayAccess methods have no return types. That is an error from PHP 8.1.
  • PHP 8.1–8.3 with Symfony 6: the lowest DoctrineBundle release doesn't match the Doctrine bridge 6.0 signature of getMappingResourceConfigDirectory(). That is a fatal error at boot.

The two Symfony 7 lowest jobs pass locally with the three dependency candidates. On GitHub they fail at install, like the Symfony 7 highest jobs, until those releases are out. As on lib-normalization-bundle#10, raising the floors would let more lowest jobs run on PHP 8, but it changes the package's requirements, so I left it out of this pull request.

The matrix gains dependency lowest next to highest, and the single PHP 7.1
lowest entry became one of those jobs. Eight exclude entries take out the 10
lowest combinations whose oldest allowed dependencies cannot run together or
on PHP 8: Symfony 4's routing annotation loader on PHP 8 (it misreads class
names), lib-object-wrapper 0.3.0 on PHP 8.1 and later (ArrayAccess return
types), and DoctrineBundle's lowest release against Symfony 6.0's Doctrine
bridge. The 14 lowest jobs that remain pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… a reader

The routing loader test no longer expects a refusal on Symfony 7. Without an
annotation reader, on Symfony 7 or where an application disabled annotations
before it, the bundle's docblock annotations are ignored and the route loads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The docblock annotation finder and the refusal built on it are removed:
RoutingAttributeLoader is back to master's code, which skips the bundle's
docblock annotations when there is no annotation reader, as on Symfony 7.
The finder's test and the 40 fixtures only it used go with it. CHANGELOG and
README say that the annotations have no effect on Symfony 7 and that the
attributes of the same name configure those routes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The new test files for classes the pull request does not change are removed
(ErrorBuilder, RestResponseListener, PathAttributeResolverRegistry, the
option entities, attribute parameter resolution, RoutingAttributeLoader and
its two fixtures), and the RestRequestHelper and EntityValidator tests are
back to master's. They were added to raise line coverage, not to test this
change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… docblock annotations

Symfony 7 reads no docblock annotations, so a leftover @RequiredPermissions
would leave its endpoint without the permission check. Loading such a route
must fail and name the attributes to use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Symfony 7 the route loader has no annotation reader, so a leftover
@RequiredPermissions would leave its endpoint without the permission check.
If the method or class docblock of a route names one of the bundle's seven
annotations, loading the route now fails with a ConfigurationException that
names them and points to the attributes of the same name. This replaces the
Doctrine-rules reader removed two commits earlier with a match on names
only, to keep the pull request at the size of the fix. Symfony 4.4 to 6.4
keep reading the annotations as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vinayak-iyer-paysera

Copy link
Copy Markdown
Contributor Author

I cut this pull request back to what Symfony 7 support needs (ee37629..9f13c4c): 91 files became 41.

  • The Symfony 7 check no longer re-implements Doctrine's docblock reader. It looks for the bundle's seven annotation names in the action's and the class's docblocks, written short, through an alias or in full, and refuses the route with a message pointing to the attribute of the same name. That drops the 262-line reader, its test and 40 fixture controllers. I kept a check because Symfony 7 would drop a @RequiredPermissions silently and leave the endpoint without its permission check. It matches names only, so a docblock naming another library's @Query would be refused too; moving that route to attributes fixes it.
  • I removed the new tests for classes this pull request doesn't change (ErrorBuilder, RestResponseListener, the option entities and a few others). They were there to raise coverage, not to test this change.

The suite, 277 tests, passes locally in 8 of the workflow's jobs, from PHP 7.1 to 8.3 and Symfony 3.4 to 7.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant