1<?php 2 3namespace JMS\SerializerBundle\Tests\DependencyInjection; 4 5use Doctrine\Common\Annotations\AnnotationReader; 6use JMS\Serializer\Handler\SubscribingHandlerInterface; 7use JMS\Serializer\SerializationContext; 8use JMS\Serializer\EventDispatcher\EventSubscriberInterface; 9use JMS\SerializerBundle\JMSSerializerBundle; 10use JMS\SerializerBundle\Tests\DependencyInjection\Fixture\ObjectUsingExpressionLanguage; 11use JMS\SerializerBundle\Tests\DependencyInjection\Fixture\ObjectUsingExpressionProperties; 12use JMS\SerializerBundle\Tests\DependencyInjection\Fixture\SimpleObject; 13use JMS\SerializerBundle\Tests\DependencyInjection\Fixture\VersionedObject; 14use PHPUnit\Framework\TestCase; 15use Symfony\Component\DependencyInjection\ContainerBuilder; 16use Symfony\Component\DependencyInjection\Definition; 17 18class JMSSerializerExtensionTest extends TestCase 19{ 20 protected function setUp() 21 { 22 $this->clearTempDir(); 23 } 24 25 protected function tearDown() 26 { 27 $this->clearTempDir(); 28 } 29 30 private function clearTempDir() 31 { 32 // clear temporary directory 33 $dir = sys_get_temp_dir() . '/serializer'; 34 if (is_dir($dir)) { 35 foreach (new \RecursiveDirectoryIterator($dir) as $file) { 36 $filename = $file->getFileName(); 37 if ('.' === $filename || '..' === $filename) { 38 continue; 39 } 40 41 @unlink($file->getPathName()); 42 } 43 44 @rmdir($dir); 45 } 46 } 47 48 public function testHasContextFactories() 49 { 50 $container = $this->getContainerForConfig(array(array())); 51 52 $factory = $container->get('jms_serializer.serialization_context_factory'); 53 $this->assertInstanceOf('JMS\Serializer\ContextFactory\SerializationContextFactoryInterface', $factory); 54 55 $factory = $container->get('jms_serializer.deserialization_context_factory'); 56 $this->assertInstanceOf('JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface', $factory); 57 } 58 59 public function testSerializerContextFactoriesAreSet() 60 { 61 $container = $this->getContainerForConfig(array(array())); 62 63 $def = $container->getDefinition('jms_serializer'); 64 $calls = $def->getMethodCalls(); 65 66 $this->assertCount(2, $calls); 67 68 $serializationCall = $calls[0]; 69 $this->assertEquals('setSerializationContextFactory', $serializationCall[0]); 70 $this->assertEquals('jms_serializer.serialization_context_factory', (string)$serializationCall[1][0]); 71 72 $serializationCall = $calls[1]; 73 $this->assertEquals('setDeserializationContextFactory', $serializationCall[0]); 74 $this->assertEquals('jms_serializer.deserialization_context_factory', (string)$serializationCall[1][0]); 75 } 76 77 public function testSerializerContextFactoriesWithId() 78 { 79 $config = array( 80 'default_context' => array( 81 'serialization' => array( 82 'id' => 'foo' 83 ), 84 'deserialization' => array( 85 'id' => 'bar' 86 ) 87 ) 88 ); 89 90 $foo = new Definition('stdClass'); 91 $foo->setPublic(true); 92 $bar = new Definition('stdClass'); 93 $bar->setPublic(true); 94 95 $container = $this->getContainerForConfig(array($config), function (ContainerBuilder $containerBuilder) use ($foo, $bar) { 96 $containerBuilder->setDefinition('foo', $foo); 97 $containerBuilder->setDefinition('bar', $bar); 98 }); 99 100 $def = $container->getDefinition('jms_serializer'); 101 $calls = $def->getMethodCalls(); 102 103 $this->assertCount(2, $calls); 104 105 $serializationCall = $calls[0]; 106 $this->assertEquals('setSerializationContextFactory', $serializationCall[0]); 107 $this->assertEquals('foo', (string)$serializationCall[1][0]); 108 109 $serializationCall = $calls[1]; 110 $this->assertEquals('setDeserializationContextFactory', $serializationCall[0]); 111 $this->assertEquals('bar', (string)$serializationCall[1][0]); 112 113 $this->assertEquals('bar', (string)$container->getAlias('jms_serializer.deserialization_context_factory')); 114 $this->assertEquals('foo', (string)$container->getAlias('jms_serializer.serialization_context_factory')); 115 } 116 117 public function testLoadWithoutTranslator() 118 { 119 $container = $this->getContainerForConfig(array(array()), function (ContainerBuilder $containerBuilder) { 120 $containerBuilder->set('translator', null); 121 $containerBuilder->getDefinition('jms_serializer.form_error_handler')->setPublic(true); 122 }); 123 124 $def = $container->getDefinition('jms_serializer.form_error_handler'); 125 $this->assertSame(null, $def->getArgument(0)); 126 } 127 128 public function testConfiguringContextFactories() 129 { 130 $container = $this->getContainerForConfig(array(array())); 131 132 $def = $container->getDefinition('jms_serializer.serialization_context_factory'); 133 $this->assertCount(0, $def->getMethodCalls()); 134 135 $def = $container->getDefinition('jms_serializer.deserialization_context_factory'); 136 $this->assertCount(0, $def->getMethodCalls()); 137 } 138 139 public function testConfiguringContextFactoriesWithParams() 140 { 141 $config = array( 142 'default_context' => array( 143 'serialization' => array( 144 'version' => 1600, 145 'serialize_null' => true, 146 'attributes' => array('x' => 1720), 147 'groups' => array('Default', 'Registration'), 148 'enable_max_depth_checks' => true, 149 ), 150 'deserialization' => array( 151 'version' => 1640, 152 'serialize_null' => false, 153 'attributes' => array('x' => 1740), 154 'groups' => array('Default', 'Profile'), 155 'enable_max_depth_checks' => true, 156 ) 157 ) 158 ); 159 160 $container = $this->getContainerForConfig(array($config)); 161 $services = [ 162 'serialization' => 'jms_serializer.serialization_context_factory', 163 'deserialization' => 'jms_serializer.deserialization_context_factory', 164 ]; 165 foreach ($services as $configKey => $serviceId) { 166 $def = $container->getDefinition($serviceId); 167 $values = $config['default_context'][$configKey]; 168 169 $this->assertSame($values['version'], $this->getDefinitionMethodCall($def, 'setVersion')[0]); 170 $this->assertSame($values['serialize_null'], $this->getDefinitionMethodCall($def, 'setSerializeNulls')[0]); 171 $this->assertSame($values['attributes'], $this->getDefinitionMethodCall($def, 'setAttributes')[0]); 172 $this->assertSame($values['groups'], $this->getDefinitionMethodCall($def, 'setGroups')[0]); 173 $this->assertSame($values['groups'], $this->getDefinitionMethodCall($def, 'setGroups')[0]); 174 $this->assertSame(array(), $this->getDefinitionMethodCall($def, 'enableMaxDepthChecks')); 175 } 176 } 177 178 public function testConfiguringContextFactoriesWithNullDefaults() 179 { 180 $config = array( 181 'default_context' => array( 182 'serialization' => array( 183 'version' => null, 184 'serialize_null' => null, 185 'attributes' => [], 186 'groups' => null, 187 ), 188 'deserialization' => array( 189 'version' => null, 190 'serialize_null' => null, 191 'attributes' => null, 192 'groups' => null, 193 ) 194 ) 195 ); 196 197 $container = $this->getContainerForConfig(array($config)); 198 $services = [ 199 'serialization' => 'jms_serializer.serialization_context_factory', 200 'deserialization' => 'jms_serializer.deserialization_context_factory', 201 ]; 202 foreach ($services as $configKey => $serviceId) { 203 $def = $container->getDefinition($serviceId); 204 $this->assertCount(0, $def->getMethodCalls()); 205 } 206 } 207 208 private function getDefinitionMethodCall(Definition $def, $method) 209 { 210 foreach ($def->getMethodCalls() as $call) { 211 if ($call[0] === $method) { 212 return $call[1]; 213 } 214 } 215 return false; 216 } 217 218 public function testLoad() 219 { 220 $container = $this->getContainerForConfig(array(array()), function (ContainerBuilder $container) { 221 $container->getDefinition('jms_serializer.doctrine_object_constructor')->setPublic(true); 222 $container->getDefinition('jms_serializer.array_collection_handler')->setPublic(true); 223 $container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->setPublic(true); 224 $container->getAlias('JMS\Serializer\SerializerInterface')->setPublic(true); 225 $container->getAlias('JMS\Serializer\ArrayTransformerInterface')->setPublic(true); 226 }); 227 228 $simpleObject = new SimpleObject('foo', 'bar'); 229 $versionedObject = new VersionedObject('foo', 'bar'); 230 $serializer = $container->get('jms_serializer'); 231 232 $this->assertTrue($container->has('JMS\Serializer\SerializerInterface'), 'Alias should be defined to allow autowiring'); 233 $this->assertTrue($container->has('JMS\Serializer\ArrayTransformerInterface'), 'Alias should be defined to allow autowiring'); 234 235 $this->assertFalse($container->getDefinition('jms_serializer.array_collection_handler')->getArgument(0)); 236 237 // the logic is inverted because arg 0 on doctrine_proxy_subscriber is $skipVirtualTypeInit = false 238 $this->assertTrue($container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->getArgument(0)); 239 $this->assertFalse($container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->getArgument(1)); 240 241 $this->assertEquals("null", $container->getDefinition('jms_serializer.doctrine_object_constructor')->getArgument(2)); 242 243 // test that all components have been wired correctly 244 $this->assertEquals(json_encode(array('name' => 'bar')), $serializer->serialize($versionedObject, 'json')); 245 $this->assertEquals($simpleObject, $serializer->deserialize($serializer->serialize($simpleObject, 'json'), get_class($simpleObject), 'json')); 246 $this->assertEquals($simpleObject, $serializer->deserialize($serializer->serialize($simpleObject, 'xml'), get_class($simpleObject), 'xml')); 247 248 $this->assertEquals(json_encode(array('name' => 'foo')), $serializer->serialize($versionedObject, 'json', SerializationContext::create()->setVersion('0.0.1'))); 249 250 $this->assertEquals(json_encode(array('name' => 'bar')), $serializer->serialize($versionedObject, 'json', SerializationContext::create()->setVersion('1.1.1'))); 251 } 252 253 public function testLoadWithOptions() 254 { 255 $container = $this->getContainerForConfig(array(array( 256 'subscribers' => [ 257 'doctrine_proxy' => [ 258 'initialize_virtual_types' => true, 259 'initialize_excluded' => true, 260 ], 261 ], 262 'object_constructors' => [ 263 'doctrine' => [ 264 'fallback_strategy' => "exception", 265 ], 266 ], 267 'handlers' => [ 268 'array_collection' => [ 269 'initialize_excluded' => true, 270 ], 271 ], 272 )), function ($container) { 273 $container->getDefinition('jms_serializer.doctrine_object_constructor')->setPublic(true); 274 $container->getDefinition('jms_serializer.array_collection_handler')->setPublic(true); 275 $container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->setPublic(true); 276 }); 277 278 $this->assertTrue($container->getDefinition('jms_serializer.array_collection_handler')->getArgument(0)); 279 280 // the logic is inverted because arg 0 on doctrine_proxy_subscriber is $skipVirtualTypeInit = false 281 $this->assertFalse($container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->getArgument(0)); 282 $this->assertTrue($container->getDefinition('jms_serializer.doctrine_proxy_subscriber')->getArgument(1)); 283 284 $this->assertEquals("exception", $container->getDefinition('jms_serializer.doctrine_object_constructor')->getArgument(2)); 285 } 286 287 public function testLoadExistentMetadataDir() 288 { 289 $container = $this->getContainerForConfig(array(array( 290 'metadata' => [ 291 'directories' => [ 292 'foo' => [ 293 'namespace_prefix' => 'foo_ns', 294 'path' => __DIR__, 295 ] 296 ] 297 ] 298 )), function ($container) { 299 $container->getDefinition('jms_serializer.metadata.file_locator')->setPublic(true); 300 }); 301 302 $fileLocatorDef = $container->getDefinition('jms_serializer.metadata.file_locator'); 303 $directories = $fileLocatorDef->getArgument(0); 304 $this->assertEquals(['foo_ns' => __DIR__], $directories); 305 } 306 307 public function testWarmUpWithDirs() 308 { 309 $container = $this->getContainerForConfig([[ 310 'metadata' => [ 311 'warmup' => [ 312 'paths' => [ 313 'included' => ['a'], 314 'excluded' => ['b'] 315 ] 316 ] 317 ] 318 ]], function ($container){ 319 $container->getDefinition('jms_serializer.cache.cache_warmer')->setPublic(true); 320 }); 321 322 $this->assertTrue($container->hasDefinition('jms_serializer.cache.cache_warmer')); 323 324 $def = $container->getDefinition('jms_serializer.cache.cache_warmer'); 325 326 $this->assertEquals(['a'], $def->getArgument(0)); 327 $this->assertEquals(['b'], $def->getArgument(2)); 328 } 329 330 public function testWarmUpWithDirsWithNoPaths() 331 { 332 $this->getContainerForConfig([[]], function ($container) { 333 $this->assertFalse($container->hasDefinition('jms_serializer.cache.cache_warmer')); 334 }); 335 } 336 337 /** 338 * @expectedException \JMS\Serializer\Exception\RuntimeException 339 * @expectedExceptionMessage The metadata directory "foo_dir" does not exist for the namespace "foo_ns" 340 */ 341 public function testLoadNotExistentMetadataDir() 342 { 343 $this->getContainerForConfig(array(array( 344 'metadata' => [ 345 'directories' => [ 346 'foo' => [ 347 'namespace_prefix' => 'foo_ns', 348 'path' => 'foo_dir', 349 ] 350 ] 351 ] 352 ))); 353 } 354 355 /** 356 * @dataProvider getJsonVisitorConfigs 357 */ 358 public function testJsonVisitorOptions($expectedOptions, $config) 359 { 360 $container = $this->getContainerForConfig(array($config), function ($container) { 361 $container->getDefinition('jms_serializer.json_serialization_visitor')->setPublic(true); 362 }); 363 $this->assertSame($expectedOptions, $container->get('jms_serializer.json_serialization_visitor')->getOptions()); 364 } 365 366 public function getJsonVisitorConfigs() 367 { 368 $configs = array(); 369 370 if (version_compare(PHP_VERSION, '5.4', '>=')) { 371 $configs[] = array(JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, array( 372 'visitors' => array( 373 'json' => array( 374 'options' => array('JSON_UNESCAPED_UNICODE', 'JSON_PRETTY_PRINT') 375 ) 376 ) 377 )); 378 379 $configs[] = array(JSON_UNESCAPED_UNICODE, array( 380 'visitors' => array( 381 'json' => array( 382 'options' => 'JSON_UNESCAPED_UNICODE' 383 ) 384 ) 385 )); 386 } 387 388 $configs[] = array(128, array( 389 'visitors' => array( 390 'json' => array( 391 'options' => 128 392 ) 393 ) 394 )); 395 396 $configs[] = array(0, array()); 397 398 return $configs; 399 } 400 401 public function testExpressionLanguage() 402 { 403 if (!interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 404 $this->markTestSkipped("The Symfony Expression Language is not available"); 405 } 406 $container = $this->getContainerForConfig(array(array())); 407 $serializer = $container->get('jms_serializer'); 408 // test that all components have been wired correctly 409 $object = new ObjectUsingExpressionLanguage('foo', true); 410 $this->assertEquals('{"name":"foo"}', $serializer->serialize($object, 'json')); 411 $object = new ObjectUsingExpressionLanguage('foo', false); 412 $this->assertEquals('{}', $serializer->serialize($object, 'json')); 413 } 414 415 public function testExpressionLanguageVirtualProperties() 416 { 417 if (!interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 418 $this->markTestSkipped("The Symfony Expression Language is not available"); 419 } 420 $container = $this->getContainerForConfig(array(array())); 421 $serializer = $container->get('jms_serializer'); 422 // test that all components have been wired correctly 423 $object = new ObjectUsingExpressionProperties('foo'); 424 $this->assertEquals('{"v_prop_name":"foo"}', $serializer->serialize($object, 'json')); 425 } 426 427 /** 428 * @expectedException \JMS\Serializer\Exception\ExpressionLanguageRequiredException 429 */ 430 public function testExpressionLanguageDisabledVirtualProperties() 431 { 432 if (!interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 433 $this->markTestSkipped("The Symfony Expression Language is not available"); 434 } 435 $container = $this->getContainerForConfig(array(array('expression_evaluator' => array('id' => null)))); 436 $serializer = $container->get('jms_serializer'); 437 // test that all components have been wired correctly 438 $object = new ObjectUsingExpressionProperties('foo'); 439 $serializer->serialize($object, 'json'); 440 } 441 442 /** 443 * @expectedException \JMS\Serializer\Exception\ExpressionLanguageRequiredException 444 * @expectedExceptionMessage To use conditional exclude/expose in JMS\SerializerBundle\Tests\DependencyInjection\Fixture\ObjectUsingExpressionLanguage you must configure the expression language. 445 */ 446 public function testExpressionLanguageNotLoaded() 447 { 448 $container = $this->getContainerForConfig(array(array('expression_evaluator' => array('id' => null)))); 449 $serializer = $container->get('jms_serializer'); 450 // test that all components have been wired correctly 451 $object = new ObjectUsingExpressionLanguage('foo', true); 452 $serializer->serialize($object, 'json'); 453 } 454 455 /** 456 * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 457 * @expectedExceptionMessage Invalid configuration for path "jms_serializer.expression_evaluator.id": You need at least symfony/expression language v2.6 or v3.0 to use the expression evaluator features 458 */ 459 public function testExpressionInvalidEvaluator() 460 { 461 if (interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 462 $this->markTestSkipped('To pass this test the "symfony/expression-language" component should be available'); 463 } 464 $this->getContainerForConfig(array(array('expression_evaluator' => array('id' => 'foo')))); 465 } 466 467 /** 468 * @dataProvider getXmlVisitorWhitelists 469 */ 470 public function testXmlVisitorOptions($expectedOptions, $config) 471 { 472 $container = $this->getContainerForConfig(array($config), function ($container) { 473 $container->getDefinition('jms_serializer.xml_deserialization_visitor')->setPublic(true); 474 }); 475 $this->assertSame($expectedOptions, $container->get('jms_serializer.xml_deserialization_visitor')->getDoctypeWhitelist()); 476 } 477 478 public function getXmlVisitorWhitelists() 479 { 480 $configs = array(); 481 482 $configs[] = array(array('good document', 'other good document'), array( 483 'visitors' => array( 484 'xml' => array( 485 'doctype_whitelist' => array('good document', 'other good document'), 486 ) 487 ) 488 )); 489 490 $configs[] = array(array(), array()); 491 492 return $configs; 493 } 494 495 public function testXmlVisitorFormatOutput() 496 { 497 $config = array( 498 'visitors' => array( 499 'xml' => array( 500 'format_output' => false, 501 ) 502 ) 503 ); 504 $container = $this->getContainerForConfig(array($config), function ($container) { 505 $container->getDefinition('jms_serializer.xml_serialization_visitor')->setPublic(true); 506 }); 507 508 $this->assertFalse($container->get('jms_serializer.xml_serialization_visitor')->isFormatOutput()); 509 } 510 511 public function testXmlVisitorDefaultValueToFormatOutput() 512 { 513 $container = $this->getContainerForConfig(array(), function ($container) { 514 $container->getDefinition('jms_serializer.xml_serialization_visitor')->setPublic(true); 515 }); 516 $this->assertTrue($container->get('jms_serializer.xml_serialization_visitor')->isFormatOutput()); 517 } 518 519 public function testAutoconfigureSubscribers() 520 { 521 $container = $this->getContainerForConfig(array()); 522 523 if (!method_exists($container, 'registerForAutoconfiguration')) { 524 $this->markTestSkipped( 525 'registerForAutoconfiguration method is not available in the container' 526 ); 527 } 528 529 $autoconfigureInstance = $container->getAutoconfiguredInstanceof(); 530 531 $this->assertTrue(array_key_exists(EventSubscriberInterface::class, $autoconfigureInstance)); 532 $this->assertTrue($autoconfigureInstance[EventSubscriberInterface::class]->hasTag('jms_serializer.event_subscriber')); 533 } 534 535 public function testAutoconfigureHandlers() 536 { 537 $container = $this->getContainerForConfig(array()); 538 539 if (!method_exists($container, 'registerForAutoconfiguration')) { 540 $this->markTestSkipped( 541 'registerForAutoconfiguration method is not available in the container' 542 ); 543 } 544 545 $autoconfigureInstance = $container->getAutoconfiguredInstanceof(); 546 547 $this->assertTrue(array_key_exists(SubscribingHandlerInterface::class, $autoconfigureInstance)); 548 $this->assertTrue($autoconfigureInstance[SubscribingHandlerInterface::class]->hasTag('jms_serializer.subscribing_handler')); 549 } 550 551 private function getContainerForConfig(array $configs, callable $configurator = null) 552 { 553 $bundle = new JMSSerializerBundle(); 554 $extension = $bundle->getContainerExtension(); 555 556 $container = new ContainerBuilder(); 557 $container->setParameter('kernel.debug', true); 558 $container->setParameter('kernel.cache_dir', sys_get_temp_dir() . '/serializer'); 559 $container->setParameter('kernel.bundles', array()); 560 $container->set('annotation_reader', new AnnotationReader()); 561 $container->setDefinition('doctrine', new Definition(Registry::class)); 562 $container->set('translator', $this->getMockBuilder('Symfony\\Component\\Translation\\TranslatorInterface')->getMock()); 563 $container->set('debug.stopwatch', $this->getMockBuilder('Symfony\\Component\\Stopwatch\\Stopwatch')->getMock()); 564 $container->registerExtension($extension); 565 $extension->load($configs, $container); 566 567 $bundle->build($container); 568 569 if ($configurator) { 570 call_user_func($configurator, $container); 571 } 572 573 $container->compile(); 574 575 return $container; 576 } 577} 578