Problem/Motivation
BackendCompilerPass overrides a service tagged backend_overridable by replacing its definition with an alias to <driver>.<service id> (or <default_backend>.<service id>). The definition, and everything attached to it, is gone. Most of the problems below follow from that one decision.
Registration order in CoreServiceProvider::register() matters for two of them. All three passes are TYPE_BEFORE_OPTIMIZATION at priority 0, so they run in registration order:
line 80 ModifyServiceDefinitionsPass (runs module service providers' alter()) line 86 ProxyServicesPass (lazy services) line 88 BackendCompilerPass
Every claim below was reproduced against Drupal 12 / PostgreSQL 18, PHP 8.5.5.
1. The tags of the overridden service are lost
- Tagged collections are built with
ContainerBuilder::findTaggedServiceIds(), which returns definitions and skips aliases, so the override inherits none of the tags. - The override never takes over the role the tags gave the service it replaces: no
logger, noneeds_destruction, noaccess_check. - A consumer that resolves a service name in a tagged service locator gets a
ServiceNotFoundException.Drupal\Core\Queue\QueueFactoryis such a consumer:#[AutowireLocator('queue_factory')]with a name that defaults toqueue.database. - Core services that are
backend_overridableplus another tag:cache_tags.invalidator.checksum,lock.persistent,session_handler.storage,path_alias.repository,logger.dblog,node.grant_storage. None is overridden in core today, which is why this stayed latent. - Fix: transfer the tags to the overriding definition, keyed by the name of the service it overrides. Not filed yet; blocks #3615202.
2. A module's alter() on an overridden service is silently discarded
ModifyServiceDefinitionsPass(line 80) runs beforeBackendCompilerPass(line 88).- A module service provider alters the core definition, then that definition is thrown away and replaced by the alias, with no warning.
- Same root cause as 1, but the tag transfer does not fix it.
- Open question: is this a bug, or the intended precedence of driver overrides over
alter()? Either way it needs a decision and documentation. Not filed.
3. Overriding a private service makes the service ID public
Drupal\Core\DependencyInjection\ContainerBuilder::setAlias()forcessetPublic(TRUE)on every alias (ContainerBuilder.php:58).- Visibility of the same service ID therefore depends on the database driver:
views.date_sqlis private on MySQL and public on PostgreSQL and SQLite. \Drupal::service('views.date_sql')works on one driver and dies on another.- Private and
backend_overridablein core:config.storage.active,menu.tree_storage,workspaces.entity.query.sql,workspaces.menu.tree_storage,views.date_sql. - The forced public is load bearing, not gratuitous:
Alias::__construct()defaults to non public andRemovePrivateAliasesPasswould then delete the service outright. A fix has to keep the alias reachable without widening the API of a service that was deliberately private. - Existing issue: #3021299 Ensure that aliased/used backend overridable are not set to private (Active).
4. Lazy (proxied) services tagged backend_overridable cannot be overridden
ProxyServicesPass(line 86) copies the definition, tags and all, todrupal.proxy_original_service.<id>and registers a fresh, untagged proxy definition at the original ID.BackendCompilerPass(line 88) then finds the tag on the renamed service and looks for<driver>.drupal.proxy_original_service.<id>, which nobody defines, sopgsql.batch.storageis silently ignored.- Affects
router.dumperandbatch.storagein core today; both arelazy: true,backend_overridableand have generated proxy classes. - Existing issue: #3461330 Lazy services (backed by proxy classes) can't be
backend_overridablebecause the proxy service isn't tagged (Active); comment #5 already identifies the cause.
Comments
Comment #2
daffie commentedDisclamer: This is was created with the help of AI.
Comment #3
longwaveRe point 4 we are hoping to remove lazy proxies in #3514491: [meta] Replace lazy service proxy generation with service closures