1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Serializer\EventDispatcher;
6
7use Doctrine\Common\Persistence\Proxy;
8use JMS\Serializer\Context;
9use JMS\Serializer\EventDispatcher\Event;
10use JMS\Serializer\EventDispatcher\EventDispatcher;
11use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
12use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
13use JMS\Serializer\EventDispatcher\ObjectEvent;
14use JMS\Serializer\Tests\Fixtures\SimpleObject;
15use JMS\Serializer\Tests\Fixtures\SimpleObjectProxy;
16use PHPUnit\Framework\Assert;
17use PHPUnit\Framework\TestCase;
18
19class EventDispatcherTest extends TestCase
20{
21    /**
22     * @var EventDispatcher
23     */
24    protected $dispatcher;
25    protected $event;
26    protected $context;
27
28    public function testHasListeners()
29    {
30        self::assertFalse($this->dispatcher->hasListeners('foo', 'Foo', 'json'));
31        $this->dispatcher->addListener('foo', static function () {
32        });
33        self::assertTrue($this->dispatcher->hasListeners('foo', 'Foo', 'json'));
34
35        self::assertFalse($this->dispatcher->hasListeners('bar', 'Bar', 'json'));
36        $this->dispatcher->addListener('bar', static function () {
37        }, 'Foo');
38        self::assertFalse($this->dispatcher->hasListeners('bar', 'Bar', 'json'));
39        $this->dispatcher->addListener('bar', static function () {
40        }, 'Bar', 'xml');
41        self::assertFalse($this->dispatcher->hasListeners('bar', 'Bar', 'json'));
42        $this->dispatcher->addListener('bar', static function () {
43        }, null, 'json');
44        self::assertTrue($this->dispatcher->hasListeners('bar', 'Baz', 'json'));
45        self::assertTrue($this->dispatcher->hasListeners('bar', 'Bar', 'json'));
46
47        self::assertFalse($this->dispatcher->hasListeners('baz', 'Bar', 'xml'));
48        $this->dispatcher->addListener('baz', static function () {
49        }, 'Bar');
50        self::assertTrue($this->dispatcher->hasListeners('baz', 'Bar', 'xml'));
51        //self::assertTrue($this->dispatcher->hasListeners('baz', 'bAr', 'xml'));
52    }
53
54    public function testDispatch()
55    {
56        $a = new MockListener();
57        $this->dispatcher->addListener('foo', [$a, 'Foo']);
58        $this->dispatch('bar');
59        $a->_verify('Listener is not called for other event.');
60
61        $b = new MockListener();
62        $this->dispatcher->addListener('pre', [$b, 'bar'], 'Bar');
63        $this->dispatcher->addListener('pre', [$b, 'foo'], 'Foo');
64        $this->dispatcher->addListener('pre', [$b, 'all']);
65
66        $b->bar($this->event, 'pre', 'Bar', 'json', $this->dispatcher);
67        $b->all($this->event, 'pre', 'Bar', 'json', $this->dispatcher);
68        $b->foo($this->event, 'pre', 'Foo', 'json', $this->dispatcher);
69        $b->all($this->event, 'pre', 'Foo', 'json', $this->dispatcher);
70
71        $b->_replay();
72        $this->dispatch('pre', 'Bar');
73        $this->dispatch('pre', 'Foo');
74        $b->_verify();
75    }
76
77    public function testDispatchWithInstanceFilteringBothListenersInvoked()
78    {
79        $a = new MockListener();
80
81        $this->dispatcher->addListener('pre', [$a, 'onlyProxy'], 'Bar', 'json', Proxy::class);
82        $this->dispatcher->addListener('pre', [$a, 'all'], 'Bar', 'json');
83
84        $object = new SimpleObjectProxy('a', 'b');
85        $event = new ObjectEvent($this->context, $object, ['name' => 'foo', 'params' => []]);
86
87        // expected
88        $a->onlyProxy($event, 'pre', 'Bar', 'json', $this->dispatcher);
89        $a->all($event, 'pre', 'Bar', 'json', $this->dispatcher);
90
91        $a->_replay();
92        $this->dispatch('pre', 'Bar', 'json', $event);
93        $a->_verify();
94    }
95
96    public function testDispatchWithInstanceFilteringOnlyGenericListenerInvoked()
97    {
98        $a = new MockListener();
99
100        $this->dispatcher->addListener('pre', [$a, 'onlyProxy'], 'Bar', 'json', Proxy::class);
101        $this->dispatcher->addListener('pre', [$a, 'all'], 'Bar', 'json');
102
103        $object = new SimpleObject('a', 'b');
104        $event = new ObjectEvent($this->context, $object, ['name' => 'foo', 'params' => []]);
105
106        // expected
107        $a->all($event, 'pre', 'Bar', 'json', $this->dispatcher);
108
109        $a->_replay();
110        $this->dispatch('pre', 'Bar', 'json', $event);
111        $a->_verify();
112    }
113
114    public function testListenerCanStopPropagation()
115    {
116        $listener1 = false;
117        $listener2 = false;
118
119        $this->dispatcher->addListener('pre', static function (Event $event) use (&$listener1) {
120            $event->stopPropagation();
121            $listener1 = true;
122        });
123
124        $this->dispatcher->addListener('pre', static function () use (&$listener2) {
125            $listener2 = true;
126        });
127
128        $this->dispatch('pre');
129
130        self::assertTrue($listener1);
131        self::assertFalse($listener2);
132    }
133
134    public function testListenerCanDispatchEvent()
135    {
136        $listener1 = false;
137        $listener2 = false;
138        $listener3 = false;
139
140        $this->dispatcher->addListener('pre', static function (Event $event, $eventName, $loweredClass, $format, EventDispatcherInterface $dispatcher) use (&$listener1) {
141            $listener1 = true;
142
143            $event = new Event($event->getContext(), $event->getType());
144
145            self::assertSame('pre', $eventName);
146            self::assertSame('json', $format);
147            self::assertSame('Foo', $loweredClass);
148
149            $dispatcher->dispatch('post', 'Blah', 'xml', $event);
150        });
151
152        $this->dispatcher->addListener('pre', static function () use (&$listener2) {
153            $listener2 = true;
154        });
155
156        $this->dispatcher->addListener('post', static function (Event $event, $eventName, $loweredClass, $format, EventDispatcherInterface $dispatcher) use (&$listener3) {
157            $listener3 = true;
158
159            self::assertSame('post', $eventName);
160            self::assertSame('xml', $format);
161            self::assertSame('Blah', $loweredClass);
162        });
163
164        $this->dispatch('pre');
165
166        self::assertTrue($listener1);
167        self::assertTrue($listener2);
168        self::assertTrue($listener3);
169    }
170
171    public function testAddSubscriber()
172    {
173        $subscriber = new MockSubscriber();
174        MockSubscriber::$events = [
175            ['event' => 'foo.bar_baz', 'format' => 'foo'],
176            ['event' => 'bar', 'method' => 'bar', 'class' => 'foo'],
177        ];
178
179        $this->dispatcher->addSubscriber($subscriber);
180        self::assertAttributeEquals([
181            'foo.bar_baz' => [
182                [[$subscriber, 'onfoobarbaz'], null, 'foo', null],
183            ],
184            'bar' => [
185                [[$subscriber, 'bar'], 'foo', null, null],
186            ],
187        ], 'listeners', $this->dispatcher);
188    }
189
190    protected function setUp()
191    {
192        $this->context = $this->getMockBuilder(Context::class)->getMock();
193
194        $this->dispatcher = $this->createEventDispatcher();
195        $this->event = new ObjectEvent($this->context, new \stdClass(), ['name' => 'foo', 'params' => []]);
196    }
197
198    protected function createEventDispatcher()
199    {
200        return new EventDispatcher();
201    }
202
203    protected function dispatch($eventName, $class = 'Foo', $format = 'json', ?Event $event = null)
204    {
205        $this->dispatcher->dispatch($eventName, $class, $format, $event ?: $this->event);
206    }
207}
208
209class MockSubscriber implements EventSubscriberInterface
210{
211    public static $events = [];
212
213    public static function getSubscribedEvents()
214    {
215        return self::$events;
216    }
217}
218
219class MockListener
220{
221    private $expected = [];
222    private $actual = [];
223    private $wasReplayed = false;
224
225    public function __call($method, array $args = [])
226    {
227        if (!$this->wasReplayed) {
228            $this->expected[] = [$method, $args];
229
230            return;
231        }
232
233        $this->actual[] = [$method, $args];
234    }
235
236    public function _replay()
237    {
238        $this->wasReplayed = true;
239    }
240
241    public function _verify($message = '')
242    {
243        Assert::assertSame($this->expected, $this->actual, $message);
244    }
245}
246