1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\Tests\Serializer\Doctrine; 6 7use Doctrine\Common\Annotations\AnnotationReader; 8use Doctrine\Common\Annotations\Reader; 9use Doctrine\Common\Persistence\AbstractManagerRegistry; 10use Doctrine\Common\Persistence\ManagerRegistry; 11use Doctrine\DBAL\Connection; 12use Doctrine\DBAL\DriverManager; 13use Doctrine\DBAL\Types\Type; 14use Doctrine\ORM\Configuration; 15use Doctrine\ORM\EntityManager; 16use Doctrine\ORM\Mapping\Driver\AnnotationDriver; 17use Doctrine\ORM\ORMException; 18use Doctrine\ORM\Tools\SchemaTool; 19use Doctrine\ORM\UnitOfWork; 20use JMS\Serializer\Builder\CallbackDriverFactory; 21use JMS\Serializer\Builder\DefaultDriverFactory; 22use JMS\Serializer\Construction\DoctrineObjectConstructor; 23use JMS\Serializer\Construction\ObjectConstructorInterface; 24use JMS\Serializer\Construction\UnserializeObjectConstructor; 25use JMS\Serializer\DeserializationContext; 26use JMS\Serializer\Metadata\ClassMetadata; 27use JMS\Serializer\Metadata\Driver\DoctrineTypeDriver; 28use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; 29use JMS\Serializer\Serializer; 30use JMS\Serializer\SerializerBuilder; 31use JMS\Serializer\SerializerInterface; 32use JMS\Serializer\Tests\Fixtures\Doctrine\Author; 33use JMS\Serializer\Tests\Fixtures\Doctrine\IdentityFields\Server; 34use JMS\Serializer\Tests\Fixtures\DoctrinePHPCR\Author as DoctrinePHPCRAuthor; 35use JMS\Serializer\Visitor\DeserializationVisitorInterface; 36use PHPUnit\Framework\TestCase; 37 38class ObjectConstructorTest extends TestCase 39{ 40 /** @var ManagerRegistry */ 41 private $registry; 42 43 /** @var Serializer */ 44 private $serializer; 45 46 /** @var DeserializationVisitorInterface */ 47 private $visitor; 48 49 /** @var DeserializationContext */ 50 private $context; 51 52 public function testFindEntity() 53 { 54 $em = $this->registry->getManager(); 55 56 $author = new Author('John', 5); 57 $em->persist($author); 58 $em->flush(); 59 $em->clear(); 60 61 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 62 63 $type = ['name' => Author::class, 'params' => []]; 64 $class = new ClassMetadata(Author::class); 65 66 $constructor = new DoctrineObjectConstructor($this->registry, $fallback); 67 $authorFetched = $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 68 69 self::assertEquals($author, $authorFetched); 70 } 71 72 public function testFindManagedEntity() 73 { 74 $em = $this->registry->getManager(); 75 76 $author = new Author('John', 5); 77 $em->persist($author); 78 $em->flush(); 79 80 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 81 82 $type = ['name' => Author::class, 'params' => []]; 83 $class = new ClassMetadata(Author::class); 84 85 $constructor = new DoctrineObjectConstructor($this->registry, $fallback); 86 $authorFetched = $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 87 88 self::assertSame($author, $authorFetched); 89 } 90 91 public function testMissingAuthor() 92 { 93 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 94 95 $type = ['name' => Author::class, 'params' => []]; 96 $class = new ClassMetadata(Author::class); 97 98 $constructor = new DoctrineObjectConstructor($this->registry, $fallback); 99 $author = $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 100 self::assertNull($author); 101 } 102 103 public function testMissingAuthorFallback() 104 { 105 $author = new Author('John'); 106 107 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 108 $fallback->expects($this->once())->method('construct')->willReturn($author); 109 110 $type = ['name' => Author::class, 'params' => []]; 111 $class = new ClassMetadata(Author::class); 112 113 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, DoctrineObjectConstructor::ON_MISSING_FALLBACK); 114 $authorFetched = $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 115 self::assertSame($author, $authorFetched); 116 } 117 118 public function testMissingNotManaged() 119 { 120 $author = new DoctrinePHPCRAuthor('foo'); 121 122 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 123 $fallback->expects($this->once())->method('construct')->willReturn($author); 124 125 $type = ['name' => Author::class, 'params' => []]; 126 $class = new ClassMetadata(Author::class); 127 128 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, DoctrineObjectConstructor::ON_MISSING_FALLBACK); 129 $authorFetched = $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 130 self::assertSame($author, $authorFetched); 131 } 132 133 public function testReference() 134 { 135 $em = $this->registry->getManager(); 136 137 $author = new Author('John', 5); 138 $em->persist($author); 139 $em->flush(); 140 141 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 142 143 $type = ['name' => Author::class, 'params' => []]; 144 $class = new ClassMetadata(Author::class); 145 146 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, DoctrineObjectConstructor::ON_MISSING_FALLBACK); 147 $authorFetched = $constructor->construct($this->visitor, $class, 5, $type, $this->context); 148 self::assertSame($author, $authorFetched); 149 } 150 151 /** 152 * @expectedException \JMS\Serializer\Exception\ObjectConstructionException 153 */ 154 public function testMissingAuthorException() 155 { 156 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 157 158 $type = ['name' => Author::class, 'params' => []]; 159 $class = new ClassMetadata(Author::class); 160 161 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, DoctrineObjectConstructor::ON_MISSING_EXCEPTION); 162 $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 163 } 164 165 /** 166 * @expectedException \JMS\Serializer\Exception\InvalidArgumentException 167 */ 168 public function testInvalidArg() 169 { 170 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 171 172 $type = ['name' => Author::class, 'params' => []]; 173 $class = new ClassMetadata(Author::class); 174 175 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, 'foo'); 176 $constructor->construct($this->visitor, $class, ['id' => 5], $type, $this->context); 177 } 178 179 public function testMissingData() 180 { 181 $author = new Author('John'); 182 183 $fallback = $this->getMockBuilder(ObjectConstructorInterface::class)->getMock(); 184 $fallback->expects($this->once())->method('construct')->willReturn($author); 185 186 $type = ['name' => Author::class, 'params' => []]; 187 $class = new ClassMetadata(Author::class); 188 189 $constructor = new DoctrineObjectConstructor($this->registry, $fallback, 'foo'); 190 $authorFetched = $constructor->construct($this->visitor, $class, ['foo' => 5], $type, $this->context); 191 self::assertSame($author, $authorFetched); 192 } 193 194 public function testNamingForIdentifierColumnIsConsidered() 195 { 196 $serializer = $this->createSerializerWithDoctrineObjectConstructor(); 197 198 /** @var EntityManager $em */ 199 $em = $this->registry->getManager(); 200 $server = new Server('Linux', '127.0.0.1', 'home'); 201 $em->persist($server); 202 $em->flush(); 203 $em->clear(); 204 205 $jsonData = '{"ip_address":"127.0.0.1", "server_id_extracted":"home", "name":"Windows"}'; 206 /** @var Server $serverDeserialized */ 207 $serverDeserialized = $serializer->deserialize($jsonData, Server::class, 'json'); 208 209 static::assertSame( 210 $em->getUnitOfWork()->getEntityState($serverDeserialized), 211 UnitOfWork::STATE_MANAGED 212 ); 213 } 214 215 protected function setUp() 216 { 217 $this->visitor = $this->getMockBuilder(DeserializationVisitorInterface::class)->getMock(); 218 $this->context = $this->getMockBuilder('JMS\Serializer\DeserializationContext')->getMock(); 219 220 $connection = $this->createConnection(); 221 $entityManager = $this->createEntityManager($connection); 222 223 $this->registry = $registry = new SimpleBaseManagerRegistry( 224 static function ($id) use ($connection, $entityManager) { 225 switch ($id) { 226 case 'default_connection': 227 return $connection; 228 229 case 'default_manager': 230 return $entityManager; 231 232 default: 233 throw new \RuntimeException(sprintf('Unknown service id "%s".', $id)); 234 } 235 } 236 ); 237 238 $this->serializer = SerializerBuilder::create() 239 ->setMetadataDriverFactory(new CallbackDriverFactory( 240 static function (array $metadataDirs, Reader $annotationReader) use ($registry) { 241 $defaultFactory = new DefaultDriverFactory(new IdenticalPropertyNamingStrategy()); 242 243 return new DoctrineTypeDriver($defaultFactory->createDriver($metadataDirs, $annotationReader), $registry); 244 } 245 )) 246 ->build(); 247 248 $this->prepareDatabase(); 249 } 250 251 private function prepareDatabase() 252 { 253 /** @var EntityManager $em */ 254 $em = $this->registry->getManager(); 255 256 $tool = new SchemaTool($em); 257 $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); 258 } 259 260 private function createConnection() 261 { 262 return DriverManager::getConnection([ 263 'driver' => 'pdo_sqlite', 264 'memory' => true, 265 ]); 266 } 267 268 private function createEntityManager(Connection $con) 269 { 270 $cfg = new Configuration(); 271 $cfg->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader(), [ 272 __DIR__ . '/../../Fixtures/Doctrine', 273 ])); 274 $cfg->setAutoGenerateProxyClasses(true); 275 $cfg->setProxyNamespace('JMS\Serializer\DoctrineProxy'); 276 $cfg->setProxyDir(sys_get_temp_dir() . '/serializer-test-proxies'); 277 278 return EntityManager::create($con, $cfg); 279 } 280 281 /** 282 * @return SerializerInterface 283 */ 284 private function createSerializerWithDoctrineObjectConstructor() 285 { 286 return SerializerBuilder::create() 287 ->setObjectConstructor( 288 new DoctrineObjectConstructor( 289 $this->registry, 290 new UnserializeObjectConstructor(), 291 DoctrineObjectConstructor::ON_MISSING_FALLBACK 292 ) 293 ) 294 ->addDefaultHandlers() 295 ->build(); 296 } 297} 298 299Type::addType('Author', 'Doctrine\DBAL\Types\StringType'); 300Type::addType('some_custom_type', 'Doctrine\DBAL\Types\StringType'); 301 302class SimpleBaseManagerRegistry extends AbstractManagerRegistry 303{ 304 private $services = []; 305 private $serviceCreator; 306 307 public function __construct($serviceCreator, $name = 'anonymous', array $connections = ['default' => 'default_connection'], array $managers = ['default' => 'default_manager'], $defaultConnection = null, $defaultManager = null, $proxyInterface = 'Doctrine\Common\Persistence\Proxy') 308 { 309 if (null === $defaultConnection) { 310 $defaultConnection = key($connections); 311 } 312 if (null === $defaultManager) { 313 $defaultManager = key($managers); 314 } 315 316 parent::__construct($name, $connections, $managers, $defaultConnection, $defaultManager, $proxyInterface); 317 318 if (!is_callable($serviceCreator)) { 319 throw new \InvalidArgumentException('$serviceCreator must be a valid callable.'); 320 } 321 $this->serviceCreator = $serviceCreator; 322 } 323 324 public function getService($name) 325 { 326 if (isset($this->services[$name])) { 327 return $this->services[$name]; 328 } 329 330 return $this->services[$name] = call_user_func($this->serviceCreator, $name); 331 } 332 333 public function resetService($name) 334 { 335 unset($this->services[$name]); 336 } 337 338 public function getAliasNamespace($alias) 339 { 340 foreach (array_keys($this->getManagers()) as $name) { 341 $manager = $this->getManager($name); 342 343 if ($manager instanceof EntityManager) { 344 try { 345 return $manager->getConfiguration()->getEntityNamespace($alias); 346 } catch (ORMException $ex) { 347 // Probably mapped by another entity manager, or invalid, just ignore this here. 348 } 349 } else { 350 throw new \LogicException(sprintf('Unsupported manager type "%s".', get_class($manager))); 351 } 352 } 353 354 throw new \RuntimeException(sprintf('The namespace alias "%s" is not known to any manager.', $alias)); 355 } 356} 357