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