Problem/Motivation

Following #3055198: [Symfony 5] Symfony/Component/EventDispatcher/Event is deprecated in Symfony 4.3 use Symfony/Contracts/EventDispatcher/Event instead and #3055194: [Symfony 5] The signature of the "Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3. Drupal core will no longer have an explicit dependency on the Symfony EventDispatcher Event class which was deprecated in Symfony 4 and replaced by an identical class in the Symfony/Contracts namespace.

However, Symfony Event's own classes (like GenericEvent) still inherit from the deprecated class, and some of Drupal's classes like EntityTypeEvent inherit from GenericEvent.

Because GenericEvent is going to stay in EventDispatcher in Symfony 5 (with the use statement updated), there is no need for any classes inheriting from GenericEvent to do anything - they'll get 'auto-updated' when we update to Symfony 5.

However, this presents a twofold problem:

1. We are currently suppressing Symfony's deprecation message, and there is no way to un-suppress this while the Symfony/EventDispatcher version is in use.

2. This means we're not able to inform people about the bridge Event class added in #3055198: [Symfony 5] Symfony/Component/EventDispatcher/Event is deprecated in Symfony 4.3 use Symfony/Contracts/EventDispatcher/Event instead until at least both of those issues are complete.

Proposed resolution

Check the inheritance chain in Drupal's ContainerAwareEventDispatcher::dispatch(), and selectively trigger deprecation messages for the deprecated Event class - we need to allow Symfony subclasses and Drupal's new event class to be used without triggering a deprecation message, since those will be changed when we update to Symfony 5, but can't be before that.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

The Symfony\Component\EventDispatcher\Event class has been deprecated and the order of parameters for dispatching events has changed, see https://www.drupal.org/node/3159012 for details.

Comments

catch created an issue. See original summary.

catch’s picture

Status: Active » Needs review
StatusFileSize
new3.17 KB
new64.51 KB

Here's an attempt, depends on the two issues mentioned above.

Test coverage is outstanding, but need to see whether this is even possible first. EventDispatcher tests themselves pass.

Status: Needs review » Needs work

The last submitted patch, 2: 3153803.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new701 bytes
new64.51 KB

Need to update the use statement in DrupalKernel.

catch’s picture

StatusFileSize
new2.47 KB
new65.95 KB

Slightly amazed that works, but not complaining.

Here's test coverage.

catch’s picture

StatusFileSize
new1.04 KB
new65.94 KB

Undoing one change here - the bc layer should I think be able to continue instantiating the old class. It shouldn't instantiate the contracts version, because that could break existing type hints. If for some reason the legacy class causes problem we could change it to the Drupal bridge Event class instead.

catch’s picture

Title: [PP-2] Find a way to notify people about Symfony Event class changes » [PP-1] [Symfony 5] Find a way to notify people about Symfony Event class changes
Issue summary: View changes
catch’s picture

Title: [PP-1] [Symfony 5] Find a way to notify people about Symfony Event class changes » [Symfony 5] Update EventDispatcher::dispatch() to make it forward-compatible with Symfony 5
catch’s picture

Issue summary: View changes
catch’s picture

Priority: Major » Critical

This is actually critical since it doesn't only add the deprecation notices but also allows contrib to use Symfony 5-style event dispatching.

andypost’s picture

CR could be polished to explain that dispatch() is not so strict

+++ b/core/core.api.php
@@ -2509,7 +2509,7 @@ function hook_validation_constraint_alter(array &$definitions) {
- * \Symfony\Component\EventDispatcher\Event object; normally you will need to
+ * \Drupal\Component\EventDispatcher\Event object; normally you will need to

+++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
@@ -6,6 +6,8 @@
 use Symfony\Component\EventDispatcher\Event;
...
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;

@@ -86,9 +88,38 @@ public function __construct(ContainerInterface $container, array $listeners = []
+      $deprecation_message = 'Symfony\Component\EventDispatcher\Event is deprecated in drupal:9.1.0 and will be replaced by Symfony\Contracts\EventDispatcher\Event in drupal:10.0.0. A new Drupal\Component\EventDispatcher\Event class is available to bridge the two versions of the class.';

+++ b/core/lib/Drupal/Component/EventDispatcher/Event.php
@@ -0,0 +1,15 @@
+use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
...
+class Event extends SymfonyEvent {}

As I see line 7 still using SF Event class?!

longwave’s picture

catch’s picture

As I see line 7 still using SF Event class?!

It is, but it's only used in the backwards compatibility layer. We can't use the contracts version, because that would break existing event listeners that type hint on the old class. We could use the new Drupal bridge class, but I there is not a pressing need to.

catch’s picture

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

Patch here was stacked on that issue, but no proper conflicts as such so just a straight re-roll with the already-applied hunks (hopefully).

edit: hmm, either I lost track of what's in each patch, or literally some bits from this patch crept into the patch on that issue - so we just have test coverage added here now...

catch’s picture

StatusFileSize
new888 bytes

Hmm even the new test method landed, is it just the use-statement change?

longwave’s picture

StatusFileSize
new4.73 KB

The new deprecation (both versions) are not yet committed, I had to manually reconstruct this as patch thinks these hunks are already committed but really it was only the first few lines of each hunk.

catch’s picture

OK I was so confused, patch relying on the first few lines to see if a hunk was already applied is... understandable but unreliable. #16 looks like what I was expecting to be left with here.

Status: Needs review » Needs work

The last submitted patch, 16: 3153803-16.patch, failed testing. View results

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new531 bytes
new5.24 KB

DrupalKernel was still using the deprecated Symfony Event class, that should help with a lot of the test failures.

kim.pepper’s picture

Can we add a “see” to the CR in the deprecation message?

catch’s picture

StatusFileSize
new5.36 KB

Add the link to the deprecation message.

kim.pepper’s picture

Nice. RTBC+1

andypost’s picture

StatusFileSize
new1.05 KB
new5.37 KB

Better to prevent to compare strings twice if not needed

catch’s picture

#23 is a good change.

krzysztof domański’s picture

StatusFileSize
new3.82 KB
new4.95 KB

Ignore

krzysztof domański’s picture

StatusFileSize
new3.82 KB
new4.95 KB

1. Combine code that triggers the same deprecation message.

// Trigger a deprecation error if the deprecated Event class is used directly.
// Also try to trigger deprecation errors when classes are in the Drupal
// namespace and inherit directly from the deprecated class. If a class is
// in the Symfony namespace or a different one, we have to assume those will
// be updated by the dependency itself. Exclude the Drupal Event bridge
// class as a special case, otherwise it's pointless.
if ($class_name === 'Symfony\Component\EventDispatcher\Event')
  || strpos($class_name, 'Drupal') !== FALSE && $class_name !== 'Drupal\Component\EventDispatcher\Event' && get_parent_class($event) === 'Symfony\Component\EventDispatcher\Event') {
  $deprecation_message = 'Symfony\Component\EventDispatcher\Event is deprecated in drupal:9.1.0 and will be replaced by Symfony\Contracts\EventDispatcher\Event in drupal:10.0.0. A new Drupal\Component\EventDispatcher\Event class is available to bridge the two versions of the class. See https://www.drupal.org/node/3159012';
  @trigger_error($deprecation_message, E_USER_DEPRECATED);
}

2. Trigger "Symfony\Component\EventDispatcher\Event is deprecated..." regardless of the argument order.

$event_dispatcher->dispatch($event_name, $event);
$event_dispatcher->dispatch($event, $event_name);
longwave’s picture

FWIW I think the combined if statement is much harder to read, I don't think it's necessary to combine here.

krzysztof domański’s picture

@longwave Thanks for review. What about #26.2?

krzysztof domański’s picture

StatusFileSize
new4.33 KB
new5.45 KB

1. Trigger "Symfony\Component\EventDispatcher\Event is deprecated..." regardless of the argument order.

$event_dispatcher->dispatch($event_name, $event);
$event_dispatcher->dispatch($event, $event_name);
krzysztof domański’s picture

StatusFileSize
new670 bytes
new5.41 KB

Fix coding standards.

krzysztof domański’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new5.37 KB

Ignore my previous patches (redundant changes).
#23 looks good. Reuploaded patch #23.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
    @@ -92,6 +92,25 @@ public function dispatch($event/*, string $event_name = NULL*/) {
           $event_name = $event_name ?? \get_class($event);
    +
    +      $class_name = get_class($event);
    

    We just had an issue that removed calls to get_class... See #2619482: Convert all get_called_class()/get_class() to static::... looks like we introduced one above already. Whoops.

    Also if we going to get the events class let's do this once.

    So I think we can do

    $class_name = $event::class;
    $event_name = $event_name ?? $class_name;
    
  2. +++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
    @@ -92,6 +92,25 @@ public function dispatch($event/*, string $event_name = NULL*/) {
    +      $deprecation_message = 'Symfony\Component\EventDispatcher\Event is deprecated in drupal:9.1.0 and will be replaced by Symfony\Contracts\EventDispatcher\Event in drupal:10.0.0. A new Drupal\Component\EventDispatcher\Event class is available to bridge the two versions of the class. See https://www.drupal.org/node/3159012';
    

    I'm umming and ahhing about this. I know that there is an effort to use PHPCS to standardise our deprecation messages so sticking them in variables doesn't help. I guess we can move this if that happens.

  3. +++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
    @@ -92,6 +92,25 @@ public function dispatch($event/*, string $event_name = NULL*/) {
    +      // Trigger a deprecation error if the deprecated Event class is used
    +      // directly.
    +      if ($class_name === 'Symfony\Component\EventDispatcher\Event') {
    +        @trigger_error($deprecation_message, E_USER_DEPRECATED);
    +      }
    +      // Also try to trigger deprecation errors when classes are in the Drupal
    +      // namespace and inherit directly from the deprecated class. If a class is
    +      // in the Symfony namespace or a different one, we have to assume those
    +      // will be updated by the dependency itself. Exclude the Drupal Event
    +      // bridge class as a special case, otherwise it's pointless.
    +      elseif ($class_name !== 'Drupal\Component\EventDispatcher\Event' && strpos($class_name, 'Drupal') !== FALSE) {
    +        if (get_parent_class($event) === 'Symfony\Component\EventDispatcher\Event') {
    +          @trigger_error($deprecation_message, E_USER_DEPRECATED);
    +        }
    +      }
    

    Can this be

          // Trigger a deprecation error if the deprecated Event class is used
          // directly.
          if ($event instanceof Event && !($event instanceof DrupalEvent)) {
            @trigger_error($deprecation_message, E_USER_DEPRECATED);
          }
    

    If we add use Drupal\Component\EventDispatcher\Event as DrupalEvent;.

    As far as I can see any Symfony events will be using the new Contracts event class. If I change the code to this the test still passes.

thalles’s picture

Hello everyone!
In Drupal\Component\EventDispatcher, Symfony\Component\EventDispatcher\Event should be replaced?

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new1.2 KB
new5.71 KB

#32.1

$class_name = $event::class;
$event_name = $event_name ?? $class_name;

This doesn't work, get_class() is correct afaik:

https://3v4l.org/lRG0v

#32.2 yes would prefer to delay duplicating the text until it's required for phpcs.

#33.3 This doesn't work - GenericEvent for example in Symfony 4, extends from the old Symfony Event class. In Symfony 5, they changed the use statement to contracts.

We can't do anything about that from Drupal, so we have to trigger a deprecation message only when the old Event class, or a direct Drupal subclass of the old event class (that isn't DrupalEvent) is dispatched.

There is a lot of implicit test coverage of this (see here where I made the change: https://www.drupal.org/project/drupal/issues/933404#comment-13758987), but no explicit test coverage, so I added some.

alexpott’s picture

@catch thanks for working through #34 and adding the extra test.

+++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
@@ -92,6 +92,25 @@ public function dispatch($event/*, string $event_name = NULL*/) {
       $event_name = $event_name ?? \get_class($event);
+
+      $class_name = get_class($event);

This can optimised - given we're going to be calling get_class() no matter what.

catch’s picture

StatusFileSize
new1.16 KB
new5.84 KB

Addressing #35, I had this change locally but it didn't make it into the patch :/

krzysztof domański’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed f3f320a and pushed to 9.1.x. Thanks!

  • alexpott committed f3f320a on 9.1.x
    Issue #3153803 by catch, Krzysztof Domański, andypost, longwave, kim....

Status: Fixed » Closed (fixed)

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