Problem/Motivation

   * Drupal's container builder can be used at runtime after compilation, so we
   * override Symfony's ContainerBuilder's restriction on setting services in a
   * frozen builder.
   *
   * @todo Restrict this to synthetic services only. Ideally, the upstream
   *   ContainerBuilder class should be fixed to allow setting synthetic
   *   services in a frozen builder.

This doesn't seem to have gotten a follow up issue and is orphaned and never addressed. Shortly after this was added almost 10 years ago to hack around Synfony, Symfony addressed the bug so its been forgotten a long long time.
#1711492: Clarify why Drupal requires a custom ContainerBuilder
https://github.com/symfony/symfony/pull/6500

Unfortunately this means we might have a sort of accidental defacto feature despite the possible problems it introduces. The point of having a compiled container is that there is some safety provided about the consistency of the environment. This is why we rebuild compiled containers instead of just mangling them with updates.

Possibly a different issue and probably more dangerous, we continued this concept out of the Builder into the compiled container which has no mutation protection allowing objects instantiated at different times to have completely different services injected.

Steps to reproduce

ContainerBuilder is probably mostly the TODO. Since we only use the builder during install, updates, and other places we're rebuilding the container, it should generally be difficult to even try to modify the compiled container.

Proposed resolution

1) Fix container builder to use parent builder with BC just in case.
2) Follow up with container fixes?

Remaining tasks

Investigate impact

Postponed on #2368263: Remove the persist service tag

User interface changes

n/a

API changes

TBD

Data model changes

n/a

Release notes snippet

TBD

CommentFileSizeAuthor
#12 3300306-12.patch3.89 KBandypost
#5 3300306-5.patch3.98 KBneclimdul

Comments

neclimdul created an issue. See original summary.

andypost’s picture

Version: 10.1.x-dev » 10.0.x-dev
neclimdul’s picture

Ah ha. I was digging around patches and the meta issues but either missed that issue somehow or missed alex bringing it up in the reviews. I'll make a patch and see the impact. If its pretty trivial we can maybe toss it back over there.

neclimdul’s picture

Status: Active » Needs review
StatusFileSize
new3.98 KB

So took a lot of back and forth yesterday with chx on slack to step through some seemingly unrelated changes required for this. Going to post a patch to kick off some tests then explain in a review.

neclimdul’s picture

  1. +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
    @@ -62,21 +63,21 @@ protected function shareService(Definition $definition, $service, $id, array &$i
    +    // Force services to be defined with lowercase identifiers. Originally
    +    // required to harden against case-insensitivity of service identifiers.
         if (strtolower($id) !== $id) {
           throw new \InvalidArgumentException("Service ID names must be lowercase: $id");
         }
    

    Updating the doc block made it clear this was never documented. Went ahead and included some docs but it looks to be cruft that could even get in the way and should probably be removed. If we want to continue to have a standard we should use static analysis to enforce it.

  2. +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
    @@ -62,21 +63,21 @@ protected function shareService(Definition $definition, $service, $id, array &$i
    +    try {
    +      parent::set($id, $service);
    +    }
    +    catch (BadMethodCallException $e) {
    +      @trigger_error($e->getMessage() . ' Doing so is deprecated in drupal:10.0.0 and the method is removed in drupal:11.0.0. Make service static or refactor service usage. See https://www.drupal.org/node/3300306', E_USER_DEPRECATED);
    +      SymfonyContainer::set($id, $service);
    +    }
    

    I could have copied the checks from symfony but this seemed maybe the best way to avoid keeping in sync with any bug fix or improvements happening up stream which is the intention. If we did this in 9 for 10 the maintenance wouldn't matter as much and copying the logic would probably be better. I don't know why I said static, I meant synthetic.

  3. +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/BackendCompilerPass.php
    @@ -49,7 +49,6 @@ public function process(ContainerBuilder $container) {
             $driver_backend = $container->get('database')->driver();
             $default_backend = $container->get('database')->databaseType();
    -        $container->set('database', NULL);
    

    This is required by cause Symfony's container builder removes the definition if you unset the service. This is a feature of it being a _builder_ so this hack doesn't work anymore. Why was this hack here and why don't we need it anymore? Stick around for a tale...

    First the reason for it existing. Without this line, the entire _serviceId sleep magic falls apparent and we suddenly start serializing database connections.

    You might be asking how could that possibly be? This has nothing to do with serialization right?

    Well our first clue is just above this change we can see the code ask the container to create the database connection but to see why we have to look at another change.

  4. +++ b/core/lib/Drupal/Core/CoreServiceProvider.php
    +++ b/core/lib/Drupal/Core/CoreServiceProvider.php
    @@ -58,6 +58,7 @@ public function register(ContainerBuilder $container) {
    
    @@ -58,6 +58,7 @@ public function register(ContainerBuilder $container) {
         // service definitions. This pass must come first so that later
         // list-building passes are operating on the post-alter services list.
         $container->addCompilerPass(new ModifyServiceDefinitionsPass());
    +    $container->addCompilerPass(new DependencySerializationTraitPass());
     
         $container->addCompilerPass(new ProxyServicesPass());
     
    @@ -93,7 +94,6 @@ public function register(ContainerBuilder $container) {
    
    @@ -93,7 +94,6 @@ public function register(ContainerBuilder $container) {
         // Register plugin managers.
         $container->addCompilerPass(new PluginManagerPass());
     
    -    $container->addCompilerPass(new DependencySerializationTraitPass());
    

    Now is where this weird change comes in. See that database driver creation happens in a compile pass before the "DependencySerializationTraitPass" has run. That means our service container doesn't have the magic that tricks Symfony into writing _serviceId onto the object. Which mean our connection doesn't have one and the house of cards collapses.

    This particular change should probably not be committed as is because we have a better fix of removing the pass entirely in #2531564: Fix leaky and brittle container serialization solution but for things to generally work for testing this is needed.

Status: Needs review » Needs work

The last submitted patch, 5: 3300306-5.patch, failed testing. View results

andypost’s picture

It looks awesome! Please add code comment why this compiler pass running there to prevent further confusion

andypost’s picture

The winner is
91x: Setting service "request_stack" for an unknown or non-synthetic service definition on a compiled container is not allowed. Doing so is deprecated in drupal:10.0.0 and the method is removed in drupal:11.0.0. Make service static or refactor service usage. See https://www.drupal.org/node/3300306

neclimdul’s picture

Oh yeah, I forgot about that. um... there's an issue to remove the persist tag on request services. Attached

So, I think the right way to handle this might be postpone this on the better fixes for some of the underlying issues and working with the BC they add.

andypost’s picture

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new3.89 KB

Status: Needs review » Needs work

The last submitted patch, 12: 3300306-12.patch, failed testing. View results

andypost’s picture

So the same 3 services!

andypost’s picture

Looks mostly it will be fixed after #2368263: Remove the persist service tag

Version: 10.0.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

mstrelan’s picture

Title: Drupal ContainerBuilder allows mutation of non-synthetic services » [PP-1] Drupal ContainerBuilder allows mutation of non-synthetic services
Component: system.module » base system
Issue summary: View changes
Status: Needs work » Postponed

Let's postpone this on #2368263: Remove the persist service tag then. I don't see any changes to system.module either so changed the component to base system.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.