1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\Construction; 6 7use Doctrine\Common\Persistence\ManagerRegistry; 8use JMS\Serializer\DeserializationContext; 9use JMS\Serializer\Exception\InvalidArgumentException; 10use JMS\Serializer\Exception\ObjectConstructionException; 11use JMS\Serializer\Metadata\ClassMetadata; 12use JMS\Serializer\Visitor\DeserializationVisitorInterface; 13 14/** 15 * Doctrine object constructor for new (or existing) objects during deserialization. 16 */ 17final class DoctrineObjectConstructor implements ObjectConstructorInterface 18{ 19 public const ON_MISSING_NULL = 'null'; 20 public const ON_MISSING_EXCEPTION = 'exception'; 21 public const ON_MISSING_FALLBACK = 'fallback'; 22 /** 23 * @var string 24 */ 25 private $fallbackStrategy; 26 27 /** 28 * @var ManagerRegistry 29 */ 30 private $managerRegistry; 31 32 /** 33 * @var ObjectConstructorInterface 34 */ 35 private $fallbackConstructor; 36 37 /** 38 * @param ManagerRegistry $managerRegistry Manager registry 39 * @param ObjectConstructorInterface $fallbackConstructor Fallback object constructor 40 */ 41 public function __construct(ManagerRegistry $managerRegistry, ObjectConstructorInterface $fallbackConstructor, string $fallbackStrategy = self::ON_MISSING_NULL) 42 { 43 $this->managerRegistry = $managerRegistry; 44 $this->fallbackConstructor = $fallbackConstructor; 45 $this->fallbackStrategy = $fallbackStrategy; 46 } 47 48 /** 49 * {@inheritdoc} 50 */ 51 public function construct(DeserializationVisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context): ?object 52 { 53 // Locate possible ObjectManager 54 $objectManager = $this->managerRegistry->getManagerForClass($metadata->name); 55 56 if (!$objectManager) { 57 // No ObjectManager found, proceed with normal deserialization 58 return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context); 59 } 60 61 // Locate possible ClassMetadata 62 $classMetadataFactory = $objectManager->getMetadataFactory(); 63 64 if ($classMetadataFactory->isTransient($metadata->name)) { 65 // No ClassMetadata found, proceed with normal deserialization 66 return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context); 67 } 68 69 // Managed entity, check for proxy load 70 if (!\is_array($data)) { 71 // Single identifier, load proxy 72 return $objectManager->getReference($metadata->name, $data); 73 } 74 75 // Fallback to default constructor if missing identifier(s) 76 $classMetadata = $objectManager->getClassMetadata($metadata->name); 77 $identifierList = []; 78 79 foreach ($classMetadata->getIdentifierFieldNames() as $name) { 80 if (isset($metadata->propertyMetadata[$name])) { 81 $dataName = $metadata->propertyMetadata[$name]->serializedName; 82 } else { 83 $dataName = $name; 84 } 85 86 if (!array_key_exists($dataName, $data)) { 87 return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context); 88 } 89 $identifierList[$name] = $data[$dataName]; 90 } 91 92 // Entity update, load it from database 93 $object = $objectManager->find($metadata->name, $identifierList); 94 95 if (null === $object) { 96 switch ($this->fallbackStrategy) { 97 case self::ON_MISSING_NULL: 98 return null; 99 case self::ON_MISSING_EXCEPTION: 100 throw new ObjectConstructionException(sprintf('Entity %s can not be found', $metadata->name)); 101 case self::ON_MISSING_FALLBACK: 102 return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context); 103 default: 104 throw new InvalidArgumentException('The provided fallback strategy for the object constructor is not valid'); 105 } 106 } 107 108 $objectManager->initializeObject($object); 109 110 return $object; 111 } 112} 113