Problem/Motivation

Since Symfony 5.2, services in the container are fully private by default and the setPrivate() method has been deprecated.

Steps to reproduce

Update symfony/dependency-injection to 5.2 and see the tests fail.

Proposed resolution

Force services to public if not otherwise specified at container build time.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#35 3187074-35-sf4.patch18.98 KBdaffie
#35 3187074-35-sf5.patch24.68 KBdaffie
#35 interdiff-3187074-33-35.txt1.37 KBdaffie
#33 3187074-33-sf4.patch20.36 KBdaffie
#33 3187074-33-sf5.patch26.05 KBdaffie
#33 interdiff-3187074-25-33.txt2.26 KBdaffie
#31 3187074-25-sf5.patch26.81 KBdaffie
#25 3187074-25-sf4.patch21.11 KBdaffie
#25 interdiff-3187074-23-25-sf4.txt11.01 KBdaffie
#25 3187074-25-sf5.patch27.54 KBdaffie
#23 3187074-23-sf5.patch16.74 KBlongwave
#23 3187074-23-sf4.patch12.33 KBlongwave
#17 interdiff.3187074.10-16.txt1.39 KBlongwave
#16 interdiff.3187074.10-16.txt17.57 KBlongwave
#16 3187074-16.patch16.89 KBlongwave
#10 interdiff.3187074.6-10.txt715 byteslongwave
#10 3187074-10.patch17.18 KBlongwave
#6 3187074-6.patch16.48 KBlongwave
#4 3187074-4.patch12.71 KBlongwave
#2 3187074-2.patch11.89 KBlongwave
#2 3187074-2-sf-5.2-test-only.patch4.86 KBlongwave
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

longwave created an issue. See original summary.

longwave’s picture

The first patch just upgrades to symfony/dependency-injection:5.2 to find the failures.

The second patch has various fixes extracted from #3161889: [META] Symfony 6 compatibility to get the tests running, let's see how this fares on the full set.

longwave’s picture

Issue summary: View changes
longwave’s picture

FileSize
12.71 KB

One more fix for some kernel tests.

In the first patch in #2, run-tests.sh can't even boot without a fatal error:

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "module_handler" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead. in /var/www/html/drupal/vendor/symfony/dependency-injection/Container.php:266

Status: Needs review » Needs work

The last submitted patch, 4: 3187074-4.patch, failed testing. View results

longwave’s picture

Status: Needs work » Needs review
FileSize
16.48 KB

Fixed two more tests.

No idea what is up with InlineBlockPrivateFilesTest or InlineBlockTest.

Status: Needs review » Needs work

The last submitted patch, 6: 3187074-6.patch, failed testing. View results

Gábor Hojtsy’s picture

Title: [Symfony 5] Services are private by default » [Symfony 6] Services are private by default
Issue tags: +Symfony 6

My understanding is the existing code works fine with Symfony 5 other than deprecation notices, so this would be for Symfony 6 compatibility.

longwave’s picture

No, it doesn't work at all - we get fatal errors before we even run any tests with the 5.2 dependency injection component, see the first patch in #2.

longwave’s picture

Status: Needs work » Needs review
FileSize
17.18 KB
715 bytes

Figured out a fix for the inline block tests, however I'm not sure how we can notify contrib that they might need to add setPublic() calls to their service providers, or if we can do this in another way.

Gábor Hojtsy’s picture

Title: [Symfony 6] Services are private by default » [Symfony 5] Services are private by default

Re #9 how is this considered backwards compatible within Symfony? Are we doing our own sauce too much again? Either way, restored the title and added a Symfony 5 tag too. Sorry for the noise.

longwave’s picture

Symfony services have been private by default since 3.4, but Drupal still uses public by default - we need public services because we directly access the container in lots of places such as plugin factory methods.

There has been this sort of halfway house BC layer in Symfony where services could be declared both public and private and that still worked for us, but that layer has now been removed in Symfony 5.2 and this is what we are running into.

alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
    @@ -88,7 +88,9 @@ public function register($id, $class = null) {
    +    $definition->setPublic(TRUE);
    

    I think we need a decent comment here.

  2. +++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
    @@ -117,7 +117,7 @@ private function parseDefinitions($content, $file)
    -            list($provider, ) = explode('.', $basename, 2);
    +            [$provider, ] = explode('.', $basename, 2);
    

    Unrelated change - we should file an issue to fix all of core for this... as PHPStorm changes this by default.

  3. +++ b/core/modules/layout_builder/src/LayoutBuilderServiceProvider.php
    @@ -37,6 +37,7 @@ public function register(ContainerBuilder $container) {
           $definition->addTag('event_subscriber');
    +      $definition->setPublic(TRUE);
           $container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition);
    

    Is this change necessary? I don't think it should be. I think we need some functionality in \Drupal\Core\DependencyInjection\ContainerBuilder::setDefinition() still.

  4. Are our private services still private?
longwave’s picture

Re #13.3 the problem I had here is that setDefinition() could assume public by default, but then what if a caller really wants to add a private service? How would setDefinition tell the difference between a definition that should be public-by-omission vs explicitly set private? The Definition object belongs to Symfony so we can't add any extra flags to detect this.

This is the same reason ContainerBuilderTest has to change, I'm not sure this is right either but I don't see what the alternative is.

andypost’s picture

Re 14 I bet the setPrivate(false) is proper way to disallow private services, but not sugye that anyone using private services in contrib

longwave’s picture

Status: Needs work » Needs review
FileSize
16.89 KB
17.57 KB

Addressed #13.1, #13.2 in this patch.

#13.3 is still outstanding as per #14 and I am not sure what to do.

#13.4 we not changed YamlFileLoaderTest here, which proves that private services remain private, shortened version:

    $yml = <<<YAML
  example_private_service:
    class: \Drupal\Core\ExampleClass
    public: false
YAML;
...
    $this->assertFalse($builder->getDefinition('example_private_service')->isPublic());
    $builder->compile();
    $this->assertFalse($builder->has('example_private_service'));
longwave’s picture

FileSize
1.39 KB

Wrong interdiff above, sorry - correct one is here.

longwave’s picture

Re #15 the setPrivate(false) used to work until SF 5.1 but now setPrivate() is fully deprecated in SF 5.2:

The code we had before in our setDefinition() implementation:

    if ($definition->isPublic()) {
      $definition->setPrivate(FALSE);
    }

This does nothing, because setPrivate() now just does this:

    public function setPrivate(bool $boolean)
    {
        trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__);

        return $this->setPublic(!$boolean);
    }

ie. setPrivate(false) now just calls setPublic(true), which we already checked in the if statement.

Gábor Hojtsy’s picture

Looks good, even though we would need and should commit a composer-update-less version.

+++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php
@@ -141,6 +141,9 @@ public static function getSkippedDeprecations() {
+      // Deprecation introduced in Symfony 5.
+      'Since symfony/dependency-injection 5.1: The signature of method "Symfony\Component\DependencyInjection\Alias::setDeprecated()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.',
+      'Since symfony/dependency-injection 5.1: The signature of method "Symfony\Component\DependencyInjection\Definition::setDeprecated()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.',

We only need this due to the composer update to prove green pass right? Otherwise does not look related.

alexpott’s picture

Status: Needs review » Needs work

I think this is the best we can do. We need a really good CR that states if you're using ServiceProviderInterface or ServiceModifierInterface to register a new definition then in order to preserve BC you need to make the service public. If you're only usages are via the container then leaving it as private is a good thing.

At some point we are going to need to bite the biscuit and use auto-wiring and use Symfony's Service Subscribers & Locators to handle \Drupal and all the service location we do in core. It's gonna be a tonne of work.

+1 #19 I don't think the skips should be part of this patch - that can be addressed by #3187857: [Symfony 6] Service deprecation messages require more fields. There's no point adding skips for things that are not addressed.

+++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
@@ -101,22 +105,6 @@ public function setAlias($alias, $id) {
-  /**
-   * {@inheritdoc}
-   */
-  public function setDefinition($id, Definition $definition) {
-    $definition = parent::setDefinition($id, $definition);
-    // As of Symfony 3.4 all definitions are private by default.
-    // \Symfony\Component\DependencyInjection\Compiler\ResolvePrivatesPassOnly
-    // removes services marked as private from the container even if they are
-    // also marked as public. Drupal requires services that are public to
-    // remain in the container and not be removed.
-    if ($definition->isPublic()) {
-      $definition->setPrivate(FALSE);
-    }
-    return $definition;
-  }

Don't we need to keep this in but skip the Sf5 / 6 deprecation?

Gábor Hojtsy’s picture

Issue tags: +Europe2020
longwave’s picture

Had a thought that I will try out in a minute: by default Definitions are both public and private by default in SF4, which is why we need that explicit setPrivate(FALSE). But we could detect this default and warn the caller via a deprecation that in Drupal 10 they will explictly need to call setPublic(TRUE) if they want a public service; if they make this change it would remove the deprecation as setPublic() does the setPrivate(FALSE) part for us.

longwave’s picture

Status: Needs work » Needs review
FileSize
12.33 KB
16.74 KB

So I tried the idea in #22 and added a deprecation that is triggered only when isPublic() and isPrivate() are both true, which as far as I can see only happens with definitions that haven't explicitly been declared either way in SF4 only.

The attached two patches are the same underlying set of changes, the SF4 patch stays on SF4 and includes a legacy test for the new deprecation, the SF5 patch upgrades to SF5 but doesn't include the legacy test as the new deprecation is never triggered (and in the D10 cycle we would remove this anyway).

There is now a failure in OptimizedPhpArrayDumperTest that I don't understand and also setDefinition() is still being called without public/private being set properly from somewhere, but leaving this for a bit and will look at it fresh another day unless someone else gets there first!

The last submitted patch, 23: 3187074-23-sf4.patch, failed testing. View results

daffie’s picture

Fixed the test failures for Symfony 4. The test failures for Symfony 5 are not related to what we are trying to fix in this issue.

The last submitted patch, 25: 3187074-25-sf5.patch, failed testing. View results

daffie’s picture

Priority: Normal » Critical
Issue tags: +Drupal 10

Since this blocks Drupal 10 it should be marked critical.

longwave’s picture

1) Drupal\Tests\Core\DependencyInjection\ContainerBuilderTest::testLegacySetDefinition
Failed asserting that false is true.

Why is this failing in the SF5 patch? Ideally I think both patches should be green here.

daffie’s picture

Why is this failing in the SF5 patch? Ideally I think both patches should be green here.

You are right @longwave, and in such a world I would be an expert in working with composer. Only I am not, therefore the patch is failing for Sympony 5. Sorry.

Gábor Hojtsy’s picture

Reparenting to the Symfony 5 meta.

daffie’s picture

FileSize
26.81 KB

The patch for Symfony 5 is the same as the one for Symfony 4, only with the symfony/dependency-injection set to ^5.2 and with the deprecation message test for setDefinition() removed. In Symfony 5 a service is either public of private, not both.
For the persons who worked on making it difficult to install Drupal with the wrong dependencies, congratulations it works.

longwave’s picture

Status: Needs review » Needs work

@daffie Thank you so much for working on this. This is looking great now, just one minor improvement:

+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php
@@ -38,13 +38,24 @@ protected function setUp(): void {
+    $kernel_basic = $this->createMiddlewareServiceDefinition(FALSE, 0);
+    $kernel_basic->setPublic(TRUE);
...
+    $kernel_three = $this->createMiddlewareServiceDefinition();
+    $kernel_three->setPublic(TRUE);
...
+    $kernel_one = $this->createMiddlewareServiceDefinition(TRUE, 10);
+    $kernel_one->setPublic(TRUE);
...
+    $kernel_two = $this->createMiddlewareServiceDefinition(TRUE, 5);
+    $kernel_two->setPublic(TRUE);

Here I think we could move ->setPublic(TRUE) to the helper method?

daffie’s picture

@longwave: Thank you for your review!

For 32: Fixed.

longwave’s picture

Thanks! Good news that this is green now on both Symfony 4 and 5. Sorry for not picking this up before, but I think as per #19/#20 we need to remove the deprecation skips from the Symfony 4 patch that is to be committed? Once that is done this is RTBC as far as I can see.

daffie’s picture

longwave’s picture

Status: Needs review » Reviewed & tested by the community

I think the SF5 patch will fail now due to the new deprecations, but assuming the SF4 patch is green that one is good to go.

The last submitted patch, 35: 3187074-35-sf5.patch, failed testing. View results

  • catch committed 91a4336 on 9.2.x
    Issue #3187074 by longwave, daffie, Gábor Hojtsy, alexpott: [Symfony 5]...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Agreed this seems like the best option for now.

Committed 91a4336 and pushed to 9.2.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.