Repository navigation
Drupal service IDs from *.services.yml - #533
Merged
Merged
Conversation
This was referenced Nov 5, 2026
Closed
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Service IDs and container parameters form their own service-container group, which every project building a Symfony container reads. No behaviour change. Assisted-by: claude-code/claude-opus-5-5
A Drupal project is detected from any of its Composer projects, and its web roots are kept. Inside a web root, services.yml and every *.services.yml declare service IDs and container parameters, so container lookups are typed, completed, and navigated. Drupal turns on the service container only: Symfony's routes, translations, events, and the rest stay off. Assisted-by: claude-code/claude-opus-5-5
A setParameter() call in Drupal is far more often a validator's or a query's than the container's, so PHP-side declarations stay Symfony's. The unknown service and parameter checks judge Symfony's app. names only, so they no longer run in a Drupal project, where they forced a parse of the whole workspace for nothing. Assisted-by: claude-code/claude-opus-5-5
Composer installs core and contrib into folders the project's .gitignore leaves out. The Drupal walk, which already indexes their PHP, now also collects their services files for the workspace index. Files under a tests folder belong to test modules no site enables, and are read only while open. Assisted-by: claude-code/claude-opus-5-5
\Drupal::service('id') and \Drupal::hasService('id') name a service ID like a container lookup does: the ID completes, goes to its declaration, and lists its references, and service() is typed by the declared class. Only the global Drupal class counts.
Assisted-by: claude-code/claude-opus-5-5
Composer installs core and contrib into the web root rather than vendor/, and overwrites them on the next update. A rename whose symbol or service ID is declared there is refused, the way one declared under vendor/ is. Assisted-by: claude-code/claude-opus-5-5
The demo module declares a parameter, looks its own service up through \Drupal::service(), and a core one through a container in create(). The assertions check each name against Drupal's own loader, core's services included. DR2 leaves the backlog. Assisted-by: claude-code/claude-opus-5-5
default.services.yml, core's scaffold sources, and the sites functional tests generate are never loaded as they stand, so they no longer declare services. Moving a class and renaming a namespace now leave core and contributed extensions alone too, as rename did. A call chained straight off \Drupal::service() is typed like an assigned one. Assisted-by: claude-code/claude-opus-5-5
In a Drupal site, moving a namespace that core or a contributed extension declares is refused, as moving one of its classes is, instead of rewriting only the custom side; elsewhere a namespace a vendor package shares is still renamed on the project's side. A rename's reference edits skip core and contributed extensions. \Drupal::service(id: 'x') is read like the positional form, assigned or chained. Assisted-by: claude-code/claude-opus-5-5
… alone One workspace holds a Laravel application at its root and a Drupal site, a Symfony application, and a WordPress plugin installed beside it. Each framework keeps working in its own project, and a Symfony application with no Drupal site gets nothing from \Drupal::service(). Assisted-by: claude-code/claude-opus-5-5
…interface \Drupal::service(FooInterface::class) is typed through core's conditional return type, also when the services file aliases the interface to a service built from another class. The playground looks the greeter up by its class name too. Assisted-by: claude-code/claude-opus-5-5
Confirming a file's typed receiver calls now runs in that file's own Composer project scope. A `\Drupal::service()` lookup in a Drupal site reads a Symfony app's PHP configurator in the same workspace; resolved under the Drupal site's scope, Symfony's configurator classes were hidden, the configurator's `set()` calls went unconfirmed, and the empty answer was cached for the file. This made the mixed-workspace test flaky, depending on which thread confirmed the file first. The NoteCounter "Try:" comment promised that hovering get() shows the service class; hover shows ContainerInterface::get() instead, so drop that part. Borrow the vendor prefixes in the class-move guard instead of cloning them. File B732 (#[\AllowDynamicProperties] is not honoured) and B733 (a service a ServiceProvider re-classes keeps its YAML class), both surfaced by typed service lookups on Drupal core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Thanks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds DR2: in a Drupal site, service IDs work the way they do in a Symfony project.
*.services.yml, wherever a services file,$container->get()or\Drupal::service()names them.\Drupal::service('entity_type.manager')and$container->get(…)are typed by the class the service is built from; a lookup by::classis typed as that class or interface.On
drupal/redirectanddrupal/flag, typed services remove three false positives and expose a few correct errors that untyped lookups hid.A refactor in the first commit
The first commit, "String-key kinds name their framework through one exhaustive match", changes no behaviour.
Today. Four call sites decide which framework a string key belongs to with an if-chain, each in its own order. A kind that no check catches silently falls through to Laravel:
Why Drupal needs a change. Drupal uses Symfony's service container keys, but none of Symfony's routes, translations or events. With the chains, that means a new check placed before
is_symfony()in all four chains, and a wrong order fails silently.After. Each kind names its framework once, in a
matchwith no catch-all, and every call site becomes a flatmatch:A new kind that is not placed now fails to compile instead of becoming Laravel. WordPress, Twig and
Middlewarekeys keep their current behaviour; every caller was traced to confirm it.What DR2 then changes. A later commit gives the container keys their own switch, which is on in both Symfony and Drupal projects, without touching the other arms:
The Drupal entity types PR adds a
KeyFramework::Drupalarm the same way.In Zed, the parts that start in a YAML or Twig file need zed-extensions/php#155; other editors already send those files to PHPantom.
Builds on #532.
Checklist
If applicable:
CHANGELOG.mdREADME.md,docs/,examples/)config-schema.json). Not applicable: this PR adds or changes no setting.the changes and why they are needed.
the changelog, and other user-facing content, and removed or corrected
anything inaccurate, unclear, or unnecessary.