1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\EventDispatcher\Subscriber; 6 7use Doctrine\Common\Persistence\Proxy; 8use Doctrine\ODM\MongoDB\PersistentCollection as MongoDBPersistentCollection; 9use Doctrine\ODM\PHPCR\PersistentCollection as PHPCRPersistentCollection; 10use Doctrine\ORM\PersistentCollection; 11use Doctrine\ORM\Proxy\Proxy as ORMProxy; 12use JMS\Serializer\EventDispatcher\EventDispatcherInterface; 13use JMS\Serializer\EventDispatcher\EventSubscriberInterface; 14use JMS\Serializer\EventDispatcher\PreSerializeEvent; 15 16final class DoctrineProxySubscriber implements EventSubscriberInterface 17{ 18 /** 19 * @var bool 20 */ 21 private $skipVirtualTypeInit = true; 22 23 /** 24 * @var bool 25 */ 26 private $initializeExcluded = false; 27 28 public function __construct(bool $skipVirtualTypeInit = true, bool $initializeExcluded = false) 29 { 30 $this->skipVirtualTypeInit = (bool) $skipVirtualTypeInit; 31 $this->initializeExcluded = (bool) $initializeExcluded; 32 } 33 34 public function onPreSerialize(PreSerializeEvent $event): void 35 { 36 $object = $event->getObject(); 37 $type = $event->getType(); 38 39 // If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not 40 // modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created, 41 // so it must be loaded if its a real class. 42 $virtualType = !class_exists($type['name'], false); 43 44 if ($object instanceof PersistentCollection 45 || $object instanceof MongoDBPersistentCollection 46 || $object instanceof PHPCRPersistentCollection 47 ) { 48 if (!$virtualType) { 49 $event->setType('ArrayCollection'); 50 } 51 52 return; 53 } 54 55 if (($this->skipVirtualTypeInit && $virtualType) || 56 (!$object instanceof Proxy && !$object instanceof ORMProxy) 57 ) { 58 return; 59 } 60 61 // do not initialize the proxy if is going to be excluded by-class by some exclusion strategy 62 if (false === $this->initializeExcluded && !$virtualType) { 63 $context = $event->getContext(); 64 $exclusionStrategy = $context->getExclusionStrategy(); 65 $metadata = $context->getMetadataFactory()->getMetadataForClass(get_parent_class($object)); 66 if (null !== $metadata && null !== $exclusionStrategy && $exclusionStrategy->shouldSkipClass($metadata, $context)) { 67 return; 68 } 69 } 70 71 $object->__load(); 72 73 if (!$virtualType) { 74 $event->setType(get_parent_class($object), $type['params']); 75 } 76 } 77 78 public function onPreSerializeTypedProxy(PreSerializeEvent $event, string $eventName, string $class, string $format, EventDispatcherInterface $dispatcher): void 79 { 80 $type = $event->getType(); 81 // is a virtual type? then there is no need to change the event name 82 if (!class_exists($type['name'], false)) { 83 return; 84 } 85 86 $object = $event->getObject(); 87 if ($object instanceof Proxy) { 88 $parentClassName = get_parent_class($object); 89 90 // check if this is already a re-dispatch 91 if (strtolower($class) !== strtolower($parentClassName)) { 92 $event->stopPropagation(); 93 $newEvent = new PreSerializeEvent($event->getContext(), $object, ['name' => $parentClassName, 'params' => $type['params']]); 94 $dispatcher->dispatch($eventName, $parentClassName, $format, $newEvent); 95 96 // update the type in case some listener changed it 97 $newType = $newEvent->getType(); 98 $event->setType($newType['name'], $newType['params']); 99 } 100 } 101 } 102 103 /** 104 * {@inheritdoc} 105 */ 106 public static function getSubscribedEvents() 107 { 108 return [ 109 ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerializeTypedProxy', 'interface' => Proxy::class], 110 ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize', 'interface' => PersistentCollection::class], 111 ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize', 'interface' => MongoDBPersistentCollection::class], 112 ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize', 'interface' => PHPCRPersistentCollection::class], 113 ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize', 'interface' => Proxy::class], 114 ]; 115 } 116} 117