You are here

ContainerAwareEventDispatcherTest.php in Drupal 8

File

core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php
View source
<?php

namespace Drupal\Tests\Component\EventDispatcher;

use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Unit tests for the ContainerAwareEventDispatcher.
 *
 * NOTE: Most of this code is a literal copy of Symfony 3.4's
 * Symfony\Component\EventDispatcher\Tests\AbstractEventDispatcherTest.
 *
 * This file does NOT follow Drupal coding standards, so as to simplify future
 * synchronizations.
 *
 * @group EventDispatcher
 */
class ContainerAwareEventDispatcherTest extends TestCase {

  /* Some pseudo events */
  const PREFOO = 'pre.foo';
  const POSTFOO = 'post.foo';
  const PREBAR = 'pre.bar';
  const POSTBAR = 'post.bar';

  /**
   * @var \Symfony\Component\EventDispatcher\EventDispatcher
   */
  private $dispatcher;
  private $listener;
  protected function setUp() {
    $this->dispatcher = $this
      ->createEventDispatcher();
    $this->listener = new TestEventListener();
  }
  protected function tearDown() {
    $this->dispatcher = NULL;
    $this->listener = NULL;
  }
  protected function createEventDispatcher() {
    $container = new Container();
    return new ContainerAwareEventDispatcher($container);
  }
  public function testGetListenersWithCallables() {

    // When passing in callables exclusively as listeners into the event
    // dispatcher constructor, the event dispatcher must not attempt to
    // resolve any services.
    $container = $this
      ->getMockBuilder(ContainerInterface::class)
      ->getMock();
    $container
      ->expects($this
      ->never())
      ->method($this
      ->anything());
    $firstListener = new CallableClass();
    $secondListener = function () {
    };
    $thirdListener = [
      new TestEventListener(),
      'preFoo',
    ];
    $listeners = [
      'test_event' => [
        0 => [
          [
            'callable' => $firstListener,
          ],
          [
            'callable' => $secondListener,
          ],
          [
            'callable' => $thirdListener,
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $actualListeners = $dispatcher
      ->getListeners();
    $expectedListeners = [
      'test_event' => [
        $firstListener,
        $secondListener,
        $thirdListener,
      ],
    ];
    $this
      ->assertSame($expectedListeners, $actualListeners);
  }
  public function testDispatchWithCallables() {

    // When passing in callables exclusively as listeners into the event
    // dispatcher constructor, the event dispatcher must not attempt to
    // resolve any services.
    $container = $this
      ->getMockBuilder(ContainerInterface::class)
      ->getMock();
    $container
      ->expects($this
      ->never())
      ->method($this
      ->anything());
    $firstListener = new CallableClass();
    $secondListener = function () {
    };
    $thirdListener = [
      new TestEventListener(),
      'preFoo',
    ];
    $listeners = [
      'test_event' => [
        0 => [
          [
            'callable' => $firstListener,
          ],
          [
            'callable' => $secondListener,
          ],
          [
            'callable' => $thirdListener,
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $dispatcher
      ->dispatch('test_event');
    $this
      ->assertTrue($thirdListener[0]->preFooInvoked);
  }
  public function testGetListenersWithServices() {
    $container = new ContainerBuilder();
    $container
      ->register('listener_service', TestEventListener::class);
    $listeners = [
      'test_event' => [
        0 => [
          [
            'service' => [
              'listener_service',
              'preFoo',
            ],
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $actualListeners = $dispatcher
      ->getListeners();
    $listenerService = $container
      ->get('listener_service');
    $expectedListeners = [
      'test_event' => [
        [
          $listenerService,
          'preFoo',
        ],
      ],
    ];
    $this
      ->assertSame($expectedListeners, $actualListeners);
  }
  public function testDispatchWithServices() {
    $container = new ContainerBuilder();
    $container
      ->register('listener_service', TestEventListener::class);
    $listeners = [
      'test_event' => [
        0 => [
          [
            'service' => [
              'listener_service',
              'preFoo',
            ],
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $dispatcher
      ->dispatch('test_event');
    $listenerService = $container
      ->get('listener_service');
    $this
      ->assertTrue($listenerService->preFooInvoked);
  }
  public function testRemoveService() {
    $container = new ContainerBuilder();
    $container
      ->register('listener_service', TestEventListener::class);
    $container
      ->register('other_listener_service', TestEventListener::class);
    $listeners = [
      'test_event' => [
        0 => [
          [
            'service' => [
              'listener_service',
              'preFoo',
            ],
          ],
          [
            'service' => [
              'other_listener_service',
              'preFoo',
            ],
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $listenerService = $container
      ->get('listener_service');
    $dispatcher
      ->removeListener('test_event', [
      $listenerService,
      'preFoo',
    ]);

    // Ensure that other service was not initialized during removal of the
    // listener service.
    $this
      ->assertFalse($container
      ->initialized('other_listener_service'));
    $dispatcher
      ->dispatch('test_event');
    $this
      ->assertFalse($listenerService->preFooInvoked);
    $otherService = $container
      ->get('other_listener_service');
    $this
      ->assertTrue($otherService->preFooInvoked);
  }
  public function testGetListenerPriorityWithServices() {
    $container = new ContainerBuilder();
    $container
      ->register('listener_service', TestEventListener::class);
    $listeners = [
      'test_event' => [
        5 => [
          [
            'service' => [
              'listener_service',
              'preFoo',
            ],
          ],
        ],
      ],
    ];
    $dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
    $listenerService = $container
      ->get('listener_service');
    $actualPriority = $dispatcher
      ->getListenerPriority('test_event', [
      $listenerService,
      'preFoo',
    ]);
    $this
      ->assertSame(5, $actualPriority);
  }
  public function testInitialState() {
    $this
      ->assertEquals([], $this->dispatcher
      ->getListeners());
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::POSTFOO));
  }
  public function testAddListener() {
    $this->dispatcher
      ->addListener('pre.foo', [
      $this->listener,
      'preFoo',
    ]);
    $this->dispatcher
      ->addListener('post.foo', [
      $this->listener,
      'postFoo',
    ]);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners());
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::POSTFOO));
    $this
      ->assertCount(1, $this->dispatcher
      ->getListeners(self::PREFOO));
    $this
      ->assertCount(1, $this->dispatcher
      ->getListeners(self::POSTFOO));
    $this
      ->assertCount(2, $this->dispatcher
      ->getListeners());
  }
  public function testGetListenersSortsByPriority() {
    $listener1 = new TestEventListener();
    $listener2 = new TestEventListener();
    $listener3 = new TestEventListener();
    $listener1->name = '1';
    $listener2->name = '2';
    $listener3->name = '3';
    $this->dispatcher
      ->addListener('pre.foo', [
      $listener1,
      'preFoo',
    ], -10);
    $this->dispatcher
      ->addListener('pre.foo', [
      $listener2,
      'preFoo',
    ], 10);
    $this->dispatcher
      ->addListener('pre.foo', [
      $listener3,
      'preFoo',
    ]);
    $expected = [
      [
        $listener2,
        'preFoo',
      ],
      [
        $listener3,
        'preFoo',
      ],
      [
        $listener1,
        'preFoo',
      ],
    ];
    $this
      ->assertSame($expected, $this->dispatcher
      ->getListeners('pre.foo'));
  }
  public function testGetAllListenersSortsByPriority() {
    $listener1 = new TestEventListener();
    $listener2 = new TestEventListener();
    $listener3 = new TestEventListener();
    $listener4 = new TestEventListener();
    $listener5 = new TestEventListener();
    $listener6 = new TestEventListener();
    $this->dispatcher
      ->addListener('pre.foo', $listener1, -10);
    $this->dispatcher
      ->addListener('pre.foo', $listener2);
    $this->dispatcher
      ->addListener('pre.foo', $listener3, 10);
    $this->dispatcher
      ->addListener('post.foo', $listener4, -10);
    $this->dispatcher
      ->addListener('post.foo', $listener5);
    $this->dispatcher
      ->addListener('post.foo', $listener6, 10);
    $expected = [
      'pre.foo' => [
        $listener3,
        $listener2,
        $listener1,
      ],
      'post.foo' => [
        $listener6,
        $listener5,
        $listener4,
      ],
    ];
    $this
      ->assertSame($expected, $this->dispatcher
      ->getListeners());
  }
  public function testGetListenerPriority() {
    $listener1 = new TestEventListener();
    $listener2 = new TestEventListener();
    $this->dispatcher
      ->addListener('pre.foo', $listener1, -10);
    $this->dispatcher
      ->addListener('pre.foo', $listener2);
    $this
      ->assertSame(-10, $this->dispatcher
      ->getListenerPriority('pre.foo', $listener1));
    $this
      ->assertSame(0, $this->dispatcher
      ->getListenerPriority('pre.foo', $listener2));
    $this
      ->assertNull($this->dispatcher
      ->getListenerPriority('pre.bar', $listener2));
    $this
      ->assertNull($this->dispatcher
      ->getListenerPriority('pre.foo', function () {
    }));
  }
  public function testDispatch() {
    $this->dispatcher
      ->addListener('pre.foo', [
      $this->listener,
      'preFoo',
    ]);
    $this->dispatcher
      ->addListener('post.foo', [
      $this->listener,
      'postFoo',
    ]);
    $this->dispatcher
      ->dispatch(self::PREFOO);
    $this
      ->assertTrue($this->listener->preFooInvoked);
    $this
      ->assertFalse($this->listener->postFooInvoked);
    $this
      ->assertInstanceOf(Event::class, $this->dispatcher
      ->dispatch('noevent'));
    $this
      ->assertInstanceOf(Event::class, $this->dispatcher
      ->dispatch(self::PREFOO));
    $event = new Event();
    $return = $this->dispatcher
      ->dispatch(self::PREFOO, $event);
    $this
      ->assertSame($event, $return);
  }
  public function testDispatchForClosure() {
    $invoked = 0;
    $listener = function () use (&$invoked) {
      ++$invoked;
    };
    $this->dispatcher
      ->addListener('pre.foo', $listener);
    $this->dispatcher
      ->addListener('post.foo', $listener);
    $this->dispatcher
      ->dispatch(self::PREFOO);
    $this
      ->assertEquals(1, $invoked);
  }
  public function testStopEventPropagation() {
    $otherListener = new TestEventListener();

    // postFoo() stops the propagation, so only one listener should
    // be executed
    // Manually set priority to enforce $this->listener to be called first
    $this->dispatcher
      ->addListener('post.foo', [
      $this->listener,
      'postFoo',
    ], 10);
    $this->dispatcher
      ->addListener('post.foo', [
      $otherListener,
      'postFoo',
    ]);
    $this->dispatcher
      ->dispatch(self::POSTFOO);
    $this
      ->assertTrue($this->listener->postFooInvoked);
    $this
      ->assertFalse($otherListener->postFooInvoked);
  }
  public function testDispatchByPriority() {
    $invoked = [];
    $listener1 = function () use (&$invoked) {
      $invoked[] = '1';
    };
    $listener2 = function () use (&$invoked) {
      $invoked[] = '2';
    };
    $listener3 = function () use (&$invoked) {
      $invoked[] = '3';
    };
    $this->dispatcher
      ->addListener('pre.foo', $listener1, -10);
    $this->dispatcher
      ->addListener('pre.foo', $listener2);
    $this->dispatcher
      ->addListener('pre.foo', $listener3, 10);
    $this->dispatcher
      ->dispatch(self::PREFOO);
    $this
      ->assertEquals([
      '3',
      '2',
      '1',
    ], $invoked);
  }
  public function testRemoveListener() {
    $this->dispatcher
      ->addListener('pre.bar', $this->listener);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREBAR));
    $this->dispatcher
      ->removeListener('pre.bar', $this->listener);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::PREBAR));
    $this->dispatcher
      ->removeListener('notExists', $this->listener);
  }
  public function testAddSubscriber() {
    $eventSubscriber = new TestEventSubscriber();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::POSTFOO));
  }
  public function testAddSubscriberWithPriorities() {
    $eventSubscriber = new TestEventSubscriber();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $eventSubscriber = new TestEventSubscriberWithPriorities();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $listeners = $this->dispatcher
      ->getListeners('pre.foo');
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertCount(2, $listeners);
    $this
      ->assertInstanceOf(TestEventSubscriberWithPriorities::class, $listeners[0][0]);
  }
  public function testAddSubscriberWithMultipleListeners() {
    $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $listeners = $this->dispatcher
      ->getListeners('pre.foo');
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertCount(2, $listeners);
    $this
      ->assertEquals('preFoo2', $listeners[0][1]);
  }
  public function testRemoveSubscriber() {
    $eventSubscriber = new TestEventSubscriber();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::POSTFOO));
    $this->dispatcher
      ->removeSubscriber($eventSubscriber);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::POSTFOO));
  }
  public function testRemoveSubscriberWithPriorities() {
    $eventSubscriber = new TestEventSubscriberWithPriorities();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this->dispatcher
      ->removeSubscriber($eventSubscriber);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::PREFOO));
  }
  public function testRemoveSubscriberWithMultipleListeners() {
    $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
    $this->dispatcher
      ->addSubscriber($eventSubscriber);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners(self::PREFOO));
    $this
      ->assertCount(2, $this->dispatcher
      ->getListeners(self::PREFOO));
    $this->dispatcher
      ->removeSubscriber($eventSubscriber);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners(self::PREFOO));
  }
  public function testEventReceivesTheDispatcherInstanceAsArgument() {
    $listener = new TestWithDispatcher();
    $this->dispatcher
      ->addListener('test', [
      $listener,
      'foo',
    ]);
    $this
      ->assertNull($listener->name);
    $this
      ->assertNull($listener->dispatcher);
    $this->dispatcher
      ->dispatch('test');
    $this
      ->assertEquals('test', $listener->name);
    $this
      ->assertSame($this->dispatcher, $listener->dispatcher);
  }

  /**
   * @see https://bugs.php.net/bug.php?id=62976
   *
   * This bug affects:
   *  - The PHP 5.3 branch for versions < 5.3.18
   *  - The PHP 5.4 branch for versions < 5.4.8
   *  - The PHP 5.5 branch is not affected
   */
  public function testWorkaroundForPhpBug62976() {
    $dispatcher = $this
      ->createEventDispatcher();
    $dispatcher
      ->addListener('bug.62976', new CallableClass());
    $dispatcher
      ->removeListener('bug.62976', function () {
    });
    $this
      ->assertTrue($dispatcher
      ->hasListeners('bug.62976'));
  }
  public function testHasListenersWhenAddedCallbackListenerIsRemoved() {
    $listener = function () {
    };
    $this->dispatcher
      ->addListener('foo', $listener);
    $this->dispatcher
      ->removeListener('foo', $listener);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners());
  }
  public function testGetListenersWhenAddedCallbackListenerIsRemoved() {
    $listener = function () {
    };
    $this->dispatcher
      ->addListener('foo', $listener);
    $this->dispatcher
      ->removeListener('foo', $listener);
    $this
      ->assertSame([], $this->dispatcher
      ->getListeners());
  }
  public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() {
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners('foo'));
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners());
  }
  public function testHasListenersIsLazy() {
    $called = 0;
    $listener = [
      function () use (&$called) {
        ++$called;
      },
      'onFoo',
    ];
    $this->dispatcher
      ->addListener('foo', $listener);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners());
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners('foo'));
    $this
      ->assertSame(0, $called);
  }
  public function testDispatchLazyListener() {
    $called = 0;
    $factory = function () use (&$called) {
      ++$called;
      return new TestWithDispatcher();
    };
    $this->dispatcher
      ->addListener('foo', [
      $factory,
      'foo',
    ]);
    $this
      ->assertSame(0, $called);
    $this->dispatcher
      ->dispatch('foo', new Event());
    $this->dispatcher
      ->dispatch('foo', new Event());
    $this
      ->assertSame(1, $called);
  }
  public function testRemoveFindsLazyListeners() {
    $test = new TestWithDispatcher();
    $factory = function () use ($test) {
      return $test;
    };
    $this->dispatcher
      ->addListener('foo', [
      $factory,
      'foo',
    ]);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners('foo'));
    $this->dispatcher
      ->removeListener('foo', [
      $test,
      'foo',
    ]);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners('foo'));
    $this->dispatcher
      ->addListener('foo', [
      $test,
      'foo',
    ]);
    $this
      ->assertTrue($this->dispatcher
      ->hasListeners('foo'));
    $this->dispatcher
      ->removeListener('foo', [
      $factory,
      'foo',
    ]);
    $this
      ->assertFalse($this->dispatcher
      ->hasListeners('foo'));
  }
  public function testPriorityFindsLazyListeners() {
    $test = new TestWithDispatcher();
    $factory = function () use ($test) {
      return $test;
    };
    $this->dispatcher
      ->addListener('foo', [
      $factory,
      'foo',
    ], 3);
    $this
      ->assertSame(3, $this->dispatcher
      ->getListenerPriority('foo', [
      $test,
      'foo',
    ]));
    $this->dispatcher
      ->removeListener('foo', [
      $factory,
      'foo',
    ]);
    $this->dispatcher
      ->addListener('foo', [
      $test,
      'foo',
    ], 5);
    $this
      ->assertSame(5, $this->dispatcher
      ->getListenerPriority('foo', [
      $factory,
      'foo',
    ]));
  }
  public function testGetLazyListeners() {
    $test = new TestWithDispatcher();
    $factory = function () use ($test) {
      return $test;
    };
    $this->dispatcher
      ->addListener('foo', [
      $factory,
      'foo',
    ], 3);
    $this
      ->assertSame([
      [
        $test,
        'foo',
      ],
    ], $this->dispatcher
      ->getListeners('foo'));
    $this->dispatcher
      ->removeListener('foo', [
      $test,
      'foo',
    ]);
    $this->dispatcher
      ->addListener('bar', [
      $factory,
      'foo',
    ], 3);
    $this
      ->assertSame([
      'bar' => [
        [
          $test,
          'foo',
        ],
      ],
    ], $this->dispatcher
      ->getListeners());
  }

}
class CallableClass {
  public function __invoke() {
  }

}
class TestEventListener {
  public $preFooInvoked = FALSE;
  public $postFooInvoked = FALSE;

  /**
   * Listener methods
   */
  public function preFoo(Event $e) {
    $this->preFooInvoked = TRUE;
  }
  public function postFoo(Event $e) {
    $this->postFooInvoked = TRUE;
    $e
      ->stopPropagation();
  }

}
class TestWithDispatcher {
  public $name;
  public $dispatcher;
  public function foo(Event $e, $name, $dispatcher) {
    $this->name = $name;
    $this->dispatcher = $dispatcher;
  }

}
class TestEventSubscriber implements EventSubscriberInterface {
  public static function getSubscribedEvents() {
    return [
      'pre.foo' => 'preFoo',
      'post.foo' => 'postFoo',
    ];
  }

}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface {
  public static function getSubscribedEvents() {
    return [
      'pre.foo' => [
        'preFoo',
        10,
      ],
      'post.foo' => [
        'postFoo',
      ],
    ];
  }

}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface {
  public static function getSubscribedEvents() {
    return [
      'pre.foo' => [
        [
          'preFoo1',
        ],
        [
          'preFoo2',
          10,
        ],
      ],
    ];
  }

}