1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Serializer\EventDispatcher\Subscriber;
6
7use JMS\Serializer\Context;
8use JMS\Serializer\EventDispatcher\EventDispatcher;
9use JMS\Serializer\EventDispatcher\PreSerializeEvent;
10use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
11use JMS\Serializer\Metadata\ClassMetadata;
12use JMS\Serializer\Tests\Fixtures\ExclusionStrategy\AlwaysExcludeExclusionStrategy;
13use JMS\Serializer\Tests\Fixtures\SimpleObject;
14use JMS\Serializer\Tests\Fixtures\SimpleObjectProxy;
15use Metadata\MetadataFactoryInterface;
16use PHPUnit\Framework\TestCase;
17
18class DoctrineProxySubscriberTest extends TestCase
19{
20    /** @var Context */
21    private $context;
22
23    /** @var DoctrineProxySubscriber */
24    private $subscriber;
25
26    /**
27     * @var EventDispatcher
28     */
29    private $dispatcher;
30
31    public function testRewritesProxyClassName()
32    {
33        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => get_class($obj), 'params' => []]);
34        $this->subscriber->onPreSerialize($event);
35
36        self::assertEquals(['name' => get_parent_class($obj), 'params' => []], $event->getType());
37        self::assertTrue($obj->__isInitialized());
38    }
39
40    public function testDoesNotRewriteCustomType()
41    {
42        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => 'FakedName', 'params' => []]);
43        $this->subscriber->onPreSerialize($event);
44
45        self::assertEquals(['name' => 'FakedName', 'params' => []], $event->getType());
46        self::assertFalse($obj->__isInitialized());
47    }
48
49    public function testExcludedPropDoesNotGetInitialized()
50    {
51        $this->context->method('getExclusionStrategy')->willReturn(new AlwaysExcludeExclusionStrategy());
52        $this->context->method('getMetadataFactory')->willReturn(new class implements MetadataFactoryInterface
53        {
54            public function getMetadataForClass($className)
55            {
56                return new ClassMetadata(SimpleObjectProxy::class);
57            }
58        });
59
60        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => SimpleObjectProxy::class, 'params' => []]);
61        $this->subscriber->onPreSerialize($event);
62
63        self::assertEquals(['name' => SimpleObjectProxy::class, 'params' => []], $event->getType());
64        self::assertFalse($obj->__isInitialized());
65    }
66
67    public function testProxyLoadingCanBeSkippedForVirtualTypes()
68    {
69        $subscriber = new DoctrineProxySubscriber(true);
70
71        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => 'FakedName', 'params' => []]);
72        $subscriber->onPreSerialize($event);
73
74        self::assertEquals(['name' => 'FakedName', 'params' => []], $event->getType());
75        self::assertFalse($obj->__isInitialized());
76    }
77
78    public function testProxyLoadingCanBeSkippedByExclusionStrategy()
79    {
80        $subscriber = new DoctrineProxySubscriber(false, false);
81
82        $factoryMock = $this->getMockBuilder(MetadataFactoryInterface::class)->getMock();
83        $factoryMock->method('getMetadataForClass')->willReturn(new ClassMetadata(SimpleObject::class));
84
85        $this->context->method('getExclusionStrategy')->willReturn(new AlwaysExcludeExclusionStrategy());
86        $this->context->method('getMetadataFactory')->willReturn($factoryMock);
87
88        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => SimpleObjectProxy::class, 'params' => []]);
89        $subscriber->onPreSerialize($event);
90        self::assertFalse($obj->__isInitialized());
91
92        // virtual types are still initialized
93        $event = $this->createEvent($obj = new SimpleObjectProxy('a', 'b'), ['name' => 'FakeName', 'params' => []]);
94        $subscriber->onPreSerialize($event);
95        self::assertTrue($obj->__isInitialized());
96    }
97
98    public function testEventTriggeredOnRealClassName()
99    {
100        $proxy = new SimpleObjectProxy('foo', 'bar');
101
102        $realClassEventTriggered1 = false;
103        $this->dispatcher->addListener('serializer.pre_serialize', static function () use (&$realClassEventTriggered1) {
104            $realClassEventTriggered1 = true;
105        }, get_parent_class($proxy));
106
107        $event = $this->createEvent($proxy, ['name' => get_class($proxy), 'params' => []]);
108        $this->dispatcher->dispatch('serializer.pre_serialize', get_class($proxy), 'json', $event);
109
110        self::assertTrue($realClassEventTriggered1);
111    }
112
113    public function testListenersCanChangeType()
114    {
115        $proxy = new SimpleObjectProxy('foo', 'bar');
116
117        $realClassEventTriggered1 = false;
118        $this->dispatcher->addListener('serializer.pre_serialize', static function (PreSerializeEvent $event) {
119            $event->setType('foo', ['bar']);
120        }, get_parent_class($proxy));
121
122        $event = $this->createEvent($proxy, ['name' => get_class($proxy), 'params' => []]);
123        $this->dispatcher->dispatch('serializer.pre_serialize', get_class($proxy), 'json', $event);
124
125        self::assertSame(['name' => 'foo', 'params' => ['bar']], $event->getType());
126    }
127
128    public function testListenersDoNotChangeTypeOnProxiesAndVirtualTypes()
129    {
130        $proxy = new SimpleObjectProxy('foo', 'bar');
131
132        $event = $this->createEvent($proxy, ['name' => 'foo', 'params' => []]);
133        $this->dispatcher->dispatch('serializer.pre_serialize', get_class($proxy), 'json', $event);
134
135        self::assertSame(['name' => 'foo', 'params' => []], $event->getType());
136    }
137
138    public function testOnPreSerializeMaintainsParams()
139    {
140        $object = new SimpleObjectProxy('foo', 'bar');
141        $type = ['name' => SimpleObjectProxy::class, 'params' => ['baz']];
142
143        $event = $this->createEvent($object, $type);
144        $this->subscriber->onPreSerialize($event);
145
146        self::assertSame(['name' => SimpleObject::class, 'params' => ['baz']], $event->getType());
147    }
148
149    protected function setUp()
150    {
151        $this->subscriber = new DoctrineProxySubscriber();
152        $this->context = $this->getMockBuilder(Context::class)->getMock();
153
154        $this->dispatcher = new EventDispatcher();
155        $this->dispatcher->addSubscriber($this->subscriber);
156    }
157
158    private function createEvent($object, array $type)
159    {
160        return new PreSerializeEvent($this->context, $object, $type);
161    }
162}
163