Skip to content

Conversation

@GaryPEGEOT
Copy link
Contributor

@GaryPEGEOT GaryPEGEOT commented Jul 23, 2025

Q A
Branch? 7.4
Bug fix? no
New feature? no
Deprecations? no
Issues Fix #54095
License MIT

Currently doesn't check for incorrect factories, but I'm not sure what would be the best approach

@carsonbot carsonbot added Status: Needs Review DependencyInjection DX DX = Developer eXperience (anything that improves the experience of using Symfony) Feature labels Jul 23, 2025
@carsonbot carsonbot added this to the 7.4 milestone Jul 23, 2025
@carsonbot carsonbot changed the title [DX][DI] Add a compiler pass to check class existence. [DependencyInjection] Add a compiler pass to check class existence. Jul 23, 2025
@OskarStark OskarStark changed the title [DependencyInjection] Add a compiler pass to check class existence. [DependencyInjection] Add a compiler pass to check class existence Jul 23, 2025
@GaryPEGEOT GaryPEGEOT force-pushed the feat/check-class-pass branch from 740f29a to 65259c9 Compare July 23, 2025 13:19
@GaryPEGEOT GaryPEGEOT requested a review from chalasr as a code owner July 23, 2025 13:19
@nicolas-grekas

This comment was marked as outdated.

@GaryPEGEOT
Copy link
Contributor Author

Can't we patch the existing compiler pass that copies the id to the class when no class is set, and make it throw when id is not a class?

But then it would not work if you define a manual id and make a typo in the class (although more of a niche case)

@GaryPEGEOT
Copy link
Contributor Author

There are cases where the class is eg string for some internal services.

So we could have something like ['mailer.transport', 'createFromDsn'] at this point? (Not a reference but the service ID as is?

@GaryPEGEOT GaryPEGEOT force-pushed the feat/check-class-pass branch from 65259c9 to 321ccff Compare July 24, 2025 08:48
@nicolas-grekas
Copy link
Member

There are many ways one can mess up with their DI config. Validating everything adds to the compilation time. That's something we always considered outside of the scope of the compilation step.

@GaryPEGEOT GaryPEGEOT changed the title [DependencyInjection] Add a compiler pass to check class existence [DependencyInjection] Update ResolveClassPass to check class existence Jul 24, 2025
@OskarStark OskarStark changed the title [DependencyInjection] Update ResolveClassPass to check class existence [DependencyInjection] Update ResolveClassPass to check class existence Jul 24, 2025
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, test cases changes look consistent with the updated behavior.

@nicolas-grekas nicolas-grekas force-pushed the feat/check-class-pass branch from 349c87c to dbd5c25 Compare July 29, 2025 18:23
@nicolas-grekas
Copy link
Member

Thank you @GaryPEGEOT.

@nicolas-grekas nicolas-grekas merged commit 3ffc9ab into symfony:7.4 Jul 29, 2025
10 of 11 checks passed
@GaryPEGEOT GaryPEGEOT deleted the feat/check-class-pass branch July 29, 2025 18:24
nicolas-grekas added a commit that referenced this pull request Jul 30, 2025
…without a class when its id is a non-existing FQCN (nicolas-grekas)

This PR was merged into the 7.4 branch.

Discussion
----------

[DependencyInjection] Deprecate registering a service without a class when its id is a non-existing FQCN

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

#61215 might be too disruptive. This turns the exception into a deprecation.

Commits
-------

7d3ad74 [DependencyInjection] Deprecate registering a service without a class when its id is a non-existing FQCN
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed in symfony#61270, but the executing order of the condition
kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not see the deprecation, but a fatal error instead.

TODO:
 * Add tests
 * Add an issue (see https://forge.typo3.org/issues/108349 for reference)
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
bnf added a commit to bnf/symfony that referenced this pull request Nov 28, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
reviewtypo3org pushed a commit to TYPO3/typo3 that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91943
Tested-by: Benjamin Franzke <ben@bnf.dev>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Moritz Ngo <moritz.ngo@kandoh.de>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Moritz Ngo <moritz.ngo@kandoh.de>
reviewtypo3org pushed a commit to TYPO3/typo3 that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91941
Tested-by: Moritz Ngo <moritz.ngo@kandoh.de>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Moritz Ngo <moritz.ngo@kandoh.de>
Tested-by: Benjamin Franzke <ben@bnf.dev>
reviewtypo3org pushed a commit to TYPO3/typo3 that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91926
Tested-by: Achim Fritz <af@achimfritz.de>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Stephan Großberndt <stephan.grossberndt@typo3.org>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Benjamin Franzke <ben@bnf.dev>
TYPO3IncTeam pushed a commit to TYPO3-CMS/core that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91943
Tested-by: Benjamin Franzke <ben@bnf.dev>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Moritz Ngo <moritz.ngo@kandoh.de>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Moritz Ngo <moritz.ngo@kandoh.de>
TYPO3IncTeam pushed a commit to TYPO3-CMS/core that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91941
Tested-by: Moritz Ngo <moritz.ngo@kandoh.de>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Moritz Ngo <moritz.ngo@kandoh.de>
Tested-by: Benjamin Franzke <ben@bnf.dev>
TYPO3IncTeam pushed a commit to TYPO3-CMS/core that referenced this pull request Dec 1, 2025
Symfony DI added [1] a non-optional `class_exists()` check in
`Symfony\Component\DependencyInjection\Compiler\ResolveClassPass`
for the versions >=7.4 (including 8.0), which is no longer guarded
by a previous `$definition instanceof ChildDefinition` condition.

This causes PHP errors when classes are (tried to be) autoloaded, that
implement interfaces which are not available (because they use them as
an optional dependency, like optional EXT:reports integration).

The change was initially implemented as a breaking change, but was
intended to be lifted to be only a deprecation later [2].

This relaxation works for classes that are not available at all, but not
for classes that are available from a autoloader perspective, but
whose static dependencies are not and are therefore not loadable.

As a solution we revert to the old class resolution algorithm from
v7.3 and enqueue this overwrite for the upstream class resolution
compiler pass, which makes the upstream compiler pass a no-op.

An upstream fix is hopefully added later on via:
symfony/symfony#62544

[1] symfony/symfony#61215
[2] symfony/symfony#62544

Releases: main, 13.4, 12.4
Resolves: #108349
Change-Id: I5d55bfdc21caf4aece95f662e21918dd3494ad58
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/91926
Tested-by: Achim Fritz <af@achimfritz.de>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Stephan Großberndt <stephan.grossberndt@typo3.org>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Benjamin Franzke <ben@bnf.dev>
bnf added a commit to bnf/symfony that referenced this pull request Dec 1, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
nicolas-grekas pushed a commit to bnf/symfony that referenced this pull request Dec 4, 2025
… PHP error

The `class_exists` check and the condition `$definition instanceof ChildDefinition`
have been swapped with symfony#61215 in order to produce an error if a service is not
available.

This error was later relaxed to a deprecation in symfony#61270, but the executing order
of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error
(like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`)
affected uses will not receive the deprecation, but a fatal error instead.
nicolas-grekas added a commit that referenced this pull request Dec 4, 2025
…t trigger a PHP error (bnf)

This PR was merged into the 7.4 branch.

Discussion
----------

[DependencyInjection] Ensure deprecation detection does not trigger a PHP error

`Exception Interface "TYPO3\CMS\Reports\StatusProviderInterface" not found`
See https://forge.typo3.org/issues/108349

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #62589
| License       | MIT

The `class_exists` check and the condition `$definition instanceof ChildDefinition` have been swapped with #61215 in order to produce an error if a service is not available.

This error was later relaxed in #61270, but the executing order of the condition kept to be `class_exists` first, then `instanceof`.

That means, if `class_exists` fails with a PHP error (like PHP Fatal Error: Interface "My\Vendor\MyInterface" not found`) affected uses will not see the deprecation, but a fatal error instead.

Commits
-------

662d28f [DependencyInjection] Ensure deprecation detection does not trigger a PHP error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DependencyInjection DX DX = Developer eXperience (anything that improves the experience of using Symfony) Status: Reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confusing error message: 'must define the "event" attribute on "kernel.event_listener" tags' when the problem was nonexistent class

6 participants