Problem/Motivation

Upgrading to Symfony 5.1 has revealed two new deprecation messages:

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 allow a single message to be set as the "deprecated" key for services and aliases, but Symfony now breaks this out into the package, version and message.

This also ties in with Symfony's new trigger_deprecation() function which accepts the same three arguments.

Steps to reproduce

Upgrade to Symfony 5.1 and run any test involving the DI container.

Proposed resolution

We could set the package automatically via the _provider tag as we know which module a service definition belongs to.

We could try to parse the version out of the message, as we have a standard format? We can't guarantee this will be used but we could fall back to empty string/null if we can't parse it?

Or, we could deprecate the single deprecated key and add separate version and message top level or child keys.

Also consider if we want to adopt Symfony's trigger_deprecation() elsewhere in core (out of scope for this issue).

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

longwave created an issue. See original summary.

longwave’s picture

Issue tags: +Symfony 6
Berdir’s picture

Deprecation on a deprecation method? That is very meta :)

catch’s picture

Or, we could deprecate the single deprecated key and add separate version and message top level or child keys.

I think we should probably go for this option.

We could try to parse the version out of the message, as we have a standard format? We can't guarantee this will be used but we could fall back to empty string/null if we can't parse it?

This seems like a good bc layer. We could potentially even fall back to the current version of the provider too?

One annoyance here is that there's only version for the since bit of the message, there's no 'will be removed in %version' bit, so it's not compatible with our format (and nor is trigger_deprecation()).

catch’s picture

One annoyance here is that there's only version for the since bit of the message, there's no 'will be removed in %version' bit, so it's not compatible with our format (and nor is trigger_deprecation()).

OK thinking about it, this is more of a blocker than an annoyance.

Here's what a service deprecation currently looks like:

  config.storage.staging:
    alias: config.storage.sync
    deprecated: The "%alias_id%" service is deprecated in drupal:8.0.0 and is removed from drupal:10.0.0. Use the "config.storage.sync" service instead. See https://www.drupal.org/node/2574957

vs. the message that trigger_deprecation() generates:
https://github.com/symfony/deprecation-contracts/blob/main/function.php

 @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);

So this change means we cannot use the container's deprecation functionality with the deprecation format that's in our coding standards.

This leaves several options:

1. Change the deprecation format to match Symfony's, only for service definitions.
2. Change the deprecation format to match Symfony's everywhere.
3. Re-implement service deprecation in our container subclasses to preserve existing functionality.
4. Try to make trigger_deprecation() more flexible upstream - for example adding a $removed_in version

#1 and #2 we can add the information that trigger_deprecation() is missing to the rest of the message string, but it loses us our current standard format.

longwave’s picture

#1 feels inconsistent

#3 feels like a lot of work for not much gain - we don't currently extend Symfony's Definition or Alias objects which I think we would have to do here? Or we could do it outside of those classes, but that feels even more messy.

#4 unsure if upstream would accept this as it's probably not much use to them

This leaves #2 which would also let us use Symfony's trigger_deprecation() instead of calling trigger_error() manually, which might be a nice DX improvement? It does lose the "removed in" version though, but does that matter? If someone receives a deprecation message they can surely just assume the target version is the next major?

As an aside, Symfony's trigger_deprecation() is wrapped in if (!function_exists('trigger_deprecation')) so we can probably provide an override with a Symfony-compatible layer if we wanted to.

Having said that...

Can we just leave everything as-is and call ->setDeprecation('', '', $message)? $package and $version are allowed to be empty in both the method and the function, does defining them really add any value to us?

catch’s picture

It does lose the "removed in" version though, but does that matter? If someone receives a deprecation message they can surely just assume the target version is the next major?

We've moved some deprecations to $next_major_version + 1 in the past, such as #3109480: Properly deprecate theme functions for Drupal 10 and #3113284: Move deprecation of REQUEST_TIME to Drupal 10. There are also issues for drupal check/upgrade status (which I can't find right now) to exclude far-future deprecations like that from reports - so that module authors can focus just on incompatibilities with the upcoming major release.

Having said that, we can put the removed in version in the message string - as long as we can make it fit grammatically, it doesn't have to be done as an arg. And trigger_deprecation() has an arbitrary $args argument for replacements which would allow us to do that elsewhere. Maybe Symfony would be open to adding an $args to setDeprecated() too.

Can we just leave everything as-is and call ->setDeprecation('', '', $message)? $package and $version are allowed to be empty in both the method and the function, does defining them really add any value to us?

Ah! I like this idea very much and think we should at least do that here to unblock Symfony 5/6, we can open up a follow-up for using trigger_deprecation() more generally.

One issue with that is we can't actually make that change until we're on Symfony 5, so it'd need to be a Drupal 10-only patch (although we could still have the patch ready).

longwave’s picture

The problem now is that this deprecation was added in Symfony 5.1, so Symfony 4.4 still has the old signature:

public function setDeprecated($status = true, $template = null)

If we can find a consistent way to detect Symfony 4 vs 5/6 we can call the method in different ways, I guess.

longwave’s picture

Status: Active » Needs review
FileSize
2.76 KB

The getDeprecation() method looks like a suitable candidate for detection.

catch’s picture

I was thinking about reflection maybe, but just checking that method seems fine - it's all internal and just a couple of places.

longwave’s picture

#3161889-100: [META] Symfony 6 compatibility shows this works with Symfony 5 as well.

catch’s picture

Status: Needs review » Reviewed & tested by the community

I've opened a Drupal 10 follow-up, and the 'Symfony 4/Symfony 5' comments are very googleable. Thought about what a longer comment might look like, but it'd just be describing what the code is doing, and we'd need to repeat it three times, what we have seems enough.

I think this is ready to go.

longwave’s picture

The other option I thought of is "@todo Remove this in [...] when we no longer support Symfony 4.x" or similar above the else clause, which has a bit more explanation, but I think it's clear enough as is.

ilgnerfagundes’s picture

FileSize
35.26 KB

I applied the patch and for me everything is correct, rtbc +1

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I discussed the version detection a bit more with @catch - I don't think the method_exists() check is elegant - but I can't come up with anything better so it seems fine.

I wonder though if we should consider changes to support package and version. Upstream YamlFileLoader does this...

                if ('deprecated' === $key) {
                    $deprecation = \is_array($value) ? $value : ['message' => $value];

                    if (!isset($deprecation['package'])) {
                        trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file);
                    }

                    if (!isset($deprecation['version'])) {
                        trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file);
                    }

                    $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']);
                }

Imo we could do:

                    $deprecation = \is_array($value) ? $value : ['message' => $value];
                    $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']);

To at least support Symfony's expected format for Sf5 (and 6). To me this is helpful because when someone reads Symfony docs about deprecating services then they will work for Drupal too. (Which feels more important than the the actual structure of the message)

longwave’s picture

Status: Needs work » Needs review
FileSize
3.38 KB

We've used the method_exists() test for major version compatibility shims elsewhere including Composer, Symfony BrowserKit and PHPUnit - it's never very clean but it's often the simplest way as there usually isn't a way to get the version of a dependency directly.

Updated with the suggestions from #16, and updated the inline comments to use @todo and link to the removal issue now it exists. Also set the package and version for cache.null in UpdateServiceProvider, which should probably have been removed in the D9 cycle - now the version is set we have more chance of picking it up in D10.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Update/UpdateServiceProvider.php
@@ -19,7 +19,14 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier
+      $definition->setDeprecated('drupal', '8.8.0', 'The "%service_id%\" service is deprecated. While updating Drupal all caches use \Drupal\Core\Update\UpdateBackend. See https://www.drupal.org/node/3066407');

I think this should be the package - ie. drupal/core

longwave’s picture

catch’s picture

Status: Needs review » Reviewed & tested by the community

Seems OK to support the Symfony format while not actually using it. So back to RTBC for me.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 47344ca and pushed to 9.2.x. Thanks!

  • alexpott committed 47344ca on 9.2.x
    Issue #3187857 by longwave, ilgnerfagundes, catch, alexpott: [Symfony 6...
andypost’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

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