diff --git a/src/JMS/Serializer/GenericSerializationVisitor.php b/src/JMS/Serializer/GenericSerializationVisitor.php index 9107ee695..96cd1a8d7 100644 --- a/src/JMS/Serializer/GenericSerializationVisitor.php +++ b/src/JMS/Serializer/GenericSerializationVisitor.php @@ -21,19 +21,42 @@ use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Exception\InvalidArgumentException; use JMS\Serializer\Metadata\PropertyMetadata; +use JMS\Serializer\Naming\PropertyNamingStrategyInterface; abstract class GenericSerializationVisitor extends AbstractVisitor { + private $navigatorStack; private $navigator; + private $rootStack; private $root; + private $dataStackStack; private $dataStack; private $data; + public function __construct(PropertyNamingStrategyInterface $namingStrategy) + { + parent::__construct($namingStrategy); + $this->navigatorStack = new \SplStack; + $this->dataStackStack = new \SplStack; + $this->rootStack = new \SplStack; + } + public function setNavigator(GraphNavigator $navigator) { + $this->navigatorStack->push($this->navigator); + $this->dataStackStack->push($this->dataStack); + $this->rootStack->push($this->root); + $this->navigator = $navigator; - $this->root = null; $this->dataStack = new \SplStack; + $this->root = null; + } + + public function endNavigator() + { + $this->navigator = $this->navigatorStack->pop(); + $this->dataStack = $this->dataStackStack->pop(); + $this->root = $this->rootStack->pop(); } /** diff --git a/src/JMS/Serializer/Serializer.php b/src/JMS/Serializer/Serializer.php index ac488c39e..3c0308ae0 100644 --- a/src/JMS/Serializer/Serializer.php +++ b/src/JMS/Serializer/Serializer.php @@ -89,8 +89,10 @@ public function serialize($data, $format, SerializationContext $context = null) $visitor->setNavigator($this->navigator); $this->navigator->accept($visitor->prepare($data), null, $context); + $result = $visitor->getResult(); + $visitor->endNavigator(); - return $visitor->getResult(); + return $result; } public function deserialize($data, $type, $format, DeserializationContext $context = null) diff --git a/src/JMS/Serializer/XmlSerializationVisitor.php b/src/JMS/Serializer/XmlSerializationVisitor.php index ddb2dc143..642100bd1 100644 --- a/src/JMS/Serializer/XmlSerializationVisitor.php +++ b/src/JMS/Serializer/XmlSerializationVisitor.php @@ -21,6 +21,7 @@ use JMS\Serializer\Exception\RuntimeException; use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\PropertyMetadata; +use JMS\Serializer\Naming\PropertyNamingStrategyInterface; /** * XmlSerializationVisitor. @@ -31,18 +32,31 @@ class XmlSerializationVisitor extends AbstractVisitor { public $document; + private $documentStack; private $navigator; + private $navigatorStack; private $defaultRootName = 'result'; private $defaultRootNamespace; private $defaultVersion = '1.0'; private $defaultEncoding = 'UTF-8'; private $stack; + private $stackStack; private $metadataStack; + private $metadataStackStack; private $currentNode; private $currentMetadata; private $hasValue; private $nullWasVisited; + public function __construct(PropertyNamingStrategyInterface $namingStrategy) + { + parent::__construct($namingStrategy); + $this->navigatorStack = new \SplStack; + $this->documentStack = new \SplStack; + $this->stackStack = new \SplStack; + $this->metadataStackStack = new \SplStack; + } + public function setDefaultRootName($name, $namespace = null) { $this->defaultRootName = $name; @@ -69,12 +83,25 @@ public function setDefaultEncoding($encoding) public function setNavigator(GraphNavigator $navigator) { + $this->navigatorStack->push($this->navigator); + $this->documentStack->push($this->document); + $this->stackStack->push($this->stack); + $this->metadataStackStack->push($this->metadataStack); + $this->navigator = $navigator; $this->document = null; $this->stack = new \SplStack; $this->metadataStack = new \SplStack; } + public function endNavigator() + { + $this->navigator = $this->navigatorStack->pop(); + $this->document = $this->documentStack->pop(); + $this->stack = $this->stackStack->pop(); + $this->metadataStack = $this->metadataStackStack->pop(); + } + public function getNavigator() { return $this->navigator; @@ -199,7 +226,7 @@ public function startVisitingObject(ClassMetadata $metadata, $data, array $type, } $this->document->appendChild($this->currentNode); } - + $this->addNamespaceAttributes($metadata, $this->currentNode); $this->hasValue = false; @@ -421,7 +448,7 @@ private function attachNullNamespace() $this->nullWasVisited = true; } } - + /** * Adds namespace attributes to the XML root element * diff --git a/src/JMS/Serializer/YamlSerializationVisitor.php b/src/JMS/Serializer/YamlSerializationVisitor.php index aa3536c50..4e4d7a758 100644 --- a/src/JMS/Serializer/YamlSerializationVisitor.php +++ b/src/JMS/Serializer/YamlSerializationVisitor.php @@ -35,8 +35,11 @@ class YamlSerializationVisitor extends AbstractVisitor public $writer; private $navigator; + private $navigatorStack; private $stack; + private $stackStack; private $metadataStack; + private $metadataStackStack; private $currentMetadata; public function __construct(PropertyNamingStrategyInterface $namingStrategy) @@ -44,16 +47,30 @@ public function __construct(PropertyNamingStrategyInterface $namingStrategy) parent::__construct($namingStrategy); $this->writer = new Writer(); + $this->navigatorStack = new \SplStack; + $this->stackStack = new \SplStack; + $this->metadataStackStack = new \SplStack; } public function setNavigator(GraphNavigator $navigator) { + $this->navigatorStack->push($this->navigator); + $this->stackStack->push($this->stack); + $this->metadataStackStack->push($this->metadataStack); + $this->navigator = $navigator; $this->writer->reset(); $this->stack = new \SplStack; $this->metadataStack = new \SplStack; } + public function endNavigator() + { + $this->navigator = $this->navigatorStack->pop(); + $this->stack = $this->stackStack->pop(); + $this->metadataStack = $this->metadataStackStack->pop(); + } + public function visitNull($data, array $type, Context $context) { if ('' === $this->writer->content) { diff --git a/tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php b/tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php index d882a3ecf..cc4484131 100644 --- a/tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php +++ b/tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php @@ -19,24 +19,14 @@ namespace JMS\Serializer\Tests\Serializer; use JMS\Serializer\Context; -use JMS\Serializer\DeserializationContext; use JMS\Serializer\GraphNavigator; use JMS\Serializer\Handler\PhpCollectionHandler; -use JMS\Serializer\SerializationContext; -use JMS\Serializer\Tests\Fixtures\DateTimeArraysObject; -use JMS\Serializer\Tests\Fixtures\Discriminator\Car; -use JMS\Serializer\Tests\Fixtures\InlineChildEmpty; -use JMS\Serializer\Tests\Fixtures\NamedDateTimeArraysObject; -use JMS\Serializer\Tests\Fixtures\Tree; -use PhpCollection\Sequence; -use Symfony\Component\Form\FormFactoryBuilder; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\IdentityTranslator; use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber; use JMS\Serializer\Handler\HandlerRegistry; use JMS\Serializer\EventDispatcher\EventDispatcher; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Common\Collections\ArrayCollection; use JMS\Serializer\Metadata\Driver\AnnotationDriver; use JMS\Serializer\Construction\UnserializeObjectConstructor; use JMS\Serializer\Handler\ArrayCollectionHandler; @@ -52,47 +42,10 @@ use JMS\Serializer\XmlDeserializationVisitor; use JMS\Serializer\XmlSerializationVisitor; use JMS\Serializer\YamlSerializationVisitor; -use JMS\Serializer\Tests\Fixtures\AccessorOrderChild; -use JMS\Serializer\Tests\Fixtures\AccessorOrderParent; -use JMS\Serializer\Tests\Fixtures\AccessorOrderMethod; use JMS\Serializer\Tests\Fixtures\Author; -use JMS\Serializer\Tests\Fixtures\Publisher; use JMS\Serializer\Tests\Fixtures\AuthorList; -use JMS\Serializer\Tests\Fixtures\AuthorReadOnly; -use JMS\Serializer\Tests\Fixtures\BlogPost; -use JMS\Serializer\Tests\Fixtures\CircularReferenceParent; -use JMS\Serializer\Tests\Fixtures\Comment; -use JMS\Serializer\Tests\Fixtures\CurrencyAwareOrder; -use JMS\Serializer\Tests\Fixtures\CurrencyAwarePrice; -use JMS\Serializer\Tests\Fixtures\CustomDeserializationObject; -use JMS\Serializer\Tests\Fixtures\GetSetObject; -use JMS\Serializer\Tests\Fixtures\GroupsObject; -use JMS\Serializer\Tests\Fixtures\InvalidGroupsObject; -use JMS\Serializer\Tests\Fixtures\IndexedCommentsBlogPost; -use JMS\Serializer\Tests\Fixtures\InlineParent; -use JMS\Serializer\Tests\Fixtures\InitializedObjectConstructor; -use JMS\Serializer\Tests\Fixtures\InitializedBlogPostConstructor; -use JMS\Serializer\Tests\Fixtures\Log; -use JMS\Serializer\Tests\Fixtures\ObjectWithLifecycleCallbacks; -use JMS\Serializer\Tests\Fixtures\ObjectWithVersionedVirtualProperties; -use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualProperties; -use JMS\Serializer\Tests\Fixtures\Order; -use JMS\Serializer\Tests\Fixtures\Price; -use JMS\Serializer\Tests\Fixtures\SimpleObject; -use JMS\Serializer\Tests\Fixtures\ObjectWithNullProperty; -use JMS\Serializer\Tests\Fixtures\SimpleObjectProxy; -use JMS\Serializer\Tests\Fixtures\Article; -use JMS\Serializer\Tests\Fixtures\Input; -use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyHash; use Metadata\MetadataFactory; -use Symfony\Component\Form\Form; -use Symfony\Component\Form\FormError; -use Symfony\Component\Validator\ConstraintViolation; -use Symfony\Component\Validator\ConstraintViolationList; use PhpCollection\Map; -use JMS\Serializer\Exclusion\DepthExclusionStrategy; -use JMS\Serializer\Tests\Fixtures\Node; -use JMS\Serializer\Tests\Fixtures\AuthorReadOnlyPerClass; abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase { @@ -105,805 +58,6 @@ abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase protected $serializationVisitors; protected $deserializationVisitors; - public function testSerializeNullArray() - { - $arr = array('foo' => 'bar', 'baz' => null, null); - - $this->assertEquals( - $this->getContent('nullable'), - $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)) - ); - } - - public function testSerializeNullObject() - { - $obj = new ObjectWithNullProperty('foo', 'bar'); - - $this->assertEquals( - $this->getContent('simple_object_nullable'), - $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)) - ); - } - - /** - * @dataProvider getTypes - */ - public function testNull($type) - { - $this->assertEquals($this->getContent('null'), $this->serialize(null), $type); - - if ($this->hasDeserializer()) { - $this->assertEquals(null, $this->deserialize($this->getContent('null'), $type)); - } - } - - public function getTypes() - { - return array( - array('NULL'), - array('integer'), - array('double'), - array('float'), - array('string'), - array('DateTime'), - ); - } - - public function testString() - { - $this->assertEquals($this->getContent('string'), $this->serialize('foo')); - - if ($this->hasDeserializer()) { - $this->assertEquals('foo', $this->deserialize($this->getContent('string'), 'string')); - } - } - - /** - * @dataProvider getBooleans - */ - public function testBooleans($strBoolean, $boolean) - { - $this->assertEquals($this->getContent('boolean_'.$strBoolean), $this->serialize($boolean)); - - if ($this->hasDeserializer()) { - $this->assertSame($boolean, $this->deserialize($this->getContent('boolean_'.$strBoolean), 'boolean')); - } - } - - public function getBooleans() - { - return array(array('true', true), array('false', false)); - } - - /** - * @dataProvider getNumerics - */ - public function testNumerics($key, $value, $type) - { - $this->assertEquals($this->getContent($key), $this->serialize($value)); - - if ($this->hasDeserializer()) { - $this->assertEquals($value, $this->deserialize($this->getContent($key), $type)); - } - } - - public function getNumerics() - { - return array( - array('integer', 1, 'integer'), - array('float', 4.533, 'double'), - array('float', 4.533, 'float'), - array('float_trailing_zero', 1.0, 'double'), - array('float_trailing_zero', 1.0, 'float'), - ); - } - - public function testSimpleObject() - { - $this->assertEquals($this->getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); - - if ($this->hasDeserializer()) { - $this->assertEquals($obj, $this->deserialize($this->getContent('simple_object'), get_class($obj))); - } - } - - public function testArrayStrings() - { - $data = array('foo', 'bar'); - $this->assertEquals($this->getContent('array_strings'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - $this->assertEquals($data, $this->deserialize($this->getContent('array_strings'), 'array')); - } - } - - public function testArrayBooleans() - { - $data = array(true, false); - $this->assertEquals($this->getContent('array_booleans'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - $this->assertEquals($data, $this->deserialize($this->getContent('array_booleans'), 'array')); - } - } - - public function testArrayIntegers() - { - $data = array(1, 3, 4); - $this->assertEquals($this->getContent('array_integers'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - $this->assertEquals($data, $this->deserialize($this->getContent('array_integers'), 'array')); - } - } - - public function testArrayFloats() - { - $data = array(1.34, 3.0, 6.42); - $this->assertEquals($this->getContent('array_floats'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - $this->assertEquals($data, $this->deserialize($this->getContent('array_floats'), 'array')); - } - } - - public function testArrayObjects() - { - $data = array(new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')); - $this->assertEquals($this->getContent('array_objects'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - $this->assertEquals($data, $this->deserialize($this->getContent('array_objects'), 'array')); - } - } - - - public function testDateTimeArrays() - { - $data = array( - new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - new \DateTime('2013-12-05 00:00:00', new \DateTimeZone('UTC')) - ); - - $object = new DateTimeArraysObject($data, $data); - $serializedObject = $this->serialize( $object ); - - $this->assertEquals($this->getContent('array_datetimes_object'), $serializedObject); - - if ($this->hasDeserializer()) { - /** @var DateTimeArraysObject $deserializedObject */ - $deserializedObject = $this->deserialize($this->getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); - - /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */ - foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - $this->assertEquals($object, $deserializedObject); - } - } - - public function testNamedDateTimeArrays() - { - $data = array( - new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - new \DateTime('2013-12-05 00:00:00', new \DateTimeZone('UTC')) - ); - - $object = new NamedDateTimeArraysObject(array('testdate1' => $data[0], 'testdate2' => $data[1])); - $serializedObject = $this->serialize( $object ); - - $this->assertEquals($this->getContent('array_named_datetimes_object'), $serializedObject); - - if ($this->hasDeserializer()) { - - // skip XML deserialization - if ($this->getFormat() === 'xml') { - return; - } - - /** @var NamedDateTimeArraysObject $deserializedObject */ - $deserializedObject = $this->deserialize($this->getContent('array_named_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\NamedDateTimeArraysObject'); - - /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */ - foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - $this->assertEquals($object, $deserializedObject); - } - } - - - public function testArrayMixed() - { - $this->assertEquals($this->getContent('array_mixed'), $this->serialize(array('foo', 1, true, new SimpleObject('foo', 'bar'), array(1, 3, true)))); - } - - /** - * @dataProvider getDateTime - * @group datetime - */ - public function testDateTime($key, $value, $type) - { - $this->assertEquals($this->getContent($key), $this->serialize($value)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent($key), $type); - - $this->assertTrue(is_object($deserialized)); - $this->assertEquals(get_class($value), get_class($deserialized)); - $this->assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); - } - } - - public function getDateTime() - { - return array( - array('date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'), - ); - } - - public function testDateInterval() - { - $duration = new \DateInterval('PT45M'); - - $this->assertEquals($this->getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); - } - - public function testBlogPost() - { - $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo')); - $post->addComment($comment = new Comment($author, 'foo')); - - $this->assertEquals($this->getContent('blog_post'), $this->serialize($post)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('blog_post'), get_class($post)); - $this->assertEquals('2011-07-30T00:00:00+0000', $this->getField($deserialized, 'createdAt')->format(\DateTime::ISO8601)); - $this->assertAttributeEquals('This is a nice title.', 'title', $deserialized); - $this->assertAttributeSame(false, 'published', $deserialized); - $this->assertAttributeSame('1edf9bf60a32d89afbb85b2be849e3ceed5f5b10', 'etag', $deserialized); - $this->assertAttributeEquals(new ArrayCollection(array($comment)), 'comments', $deserialized); - $this->assertAttributeEquals(new Sequence(array($comment)), 'comments2', $deserialized); - $this->assertAttributeEquals($author, 'author', $deserialized); - } - } - - public function testDeserializingNull() - { - $objectConstructor = new InitializedBlogPostConstructor(); - $this->serializer = new Serializer($this->factory, $this->handlerRegistry, $objectConstructor, $this->serializationVisitors, $this->deserializationVisitors, $this->dispatcher); - - $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo')); - - $this->setField($post, 'author', null); - $this->setField($post, 'publisher', null); - - $this->assertEquals($this->getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()->setSerializeNull(true)); - - $this->assertEquals('2011-07-30T00:00:00+0000', $this->getField($deserialized, 'createdAt')->format(\DateTime::ISO8601)); - $this->assertAttributeEquals('This is a nice title.', 'title', $deserialized); - $this->assertAttributeSame(false, 'published', $deserialized); - $this->assertAttributeEquals(new ArrayCollection(), 'comments', $deserialized); - $this->assertEquals(null, $this->getField($deserialized, 'author')); - } - } - - public function testReadOnly() - { - $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); - $this->assertEquals($this->getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author)); - $this->assertNull($this->getField($deserialized, 'id')); - $this->assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testReadOnlyClass() - { - $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); - $this->assertEquals($this->getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author)); - $this->assertNull($this->getField($deserialized, 'id')); - $this->assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testPrice() - { - $price = new Price(3); - $this->assertEquals($this->getContent('price'), $this->serialize($price)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('price'), get_class($price)); - $this->assertEquals(3, $this->getField($deserialized, 'price')); - } - } - - public function testOrder() - { - $order = new Order(new Price(12.34)); - $this->assertEquals($this->getContent('order'), $this->serialize($order)); - - if ($this->hasDeserializer()) { - $this->assertEquals($order, $this->deserialize($this->getContent('order'), get_class($order))); - } - } - - public function testCurrencyAwarePrice() - { - $price = new CurrencyAwarePrice(2.34); - $this->assertEquals($this->getContent('currency_aware_price'), $this->serialize($price)); - - if ($this->hasDeserializer()) { - $this->assertEquals($price, $this->deserialize($this->getContent('currency_aware_price'), get_class($price))); - } - } - - public function testOrderWithCurrencyAwarePrice() - { - $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); - $this->assertEquals($this->getContent('order_with_currency_aware_price'), $this->serialize($order)); - - if ($this->hasDeserializer()) { - $this->assertEquals($order, $this->deserialize($this->getContent('order_with_currency_aware_price'), get_class($order))); - } - } - - /** - * @group handlerCallback - */ - public function testArticle() - { - $article = new Article(); - $article->element = 'custom'; - $article->value = 'serialized'; - - $result = $this->serialize($article); - $this->assertEquals($this->getContent('article'), $result); - - if ($this->hasDeserializer()) { - $this->assertEquals($article, $this->deserialize($result, 'JMS\Serializer\Tests\Fixtures\Article')); - } - } - - public function testInline() - { - $inline = new InlineParent(); - - $result = $this->serialize($inline); - $this->assertEquals($this->getContent('inline'), $result); - - // no deserialization support - } - - public function testInlineEmptyChild() - { - $inline = new InlineParent(new InlineChildEmpty()); - - $result = $this->serialize($inline); - $this->assertEquals($this->getContent('inline_child_empty'), $result); - - // no deserialization support - } - - /** - * @group log - */ - public function testLog() - { - $this->assertEquals($this->getContent('log'), $this->serialize($log = new Log())); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('log'), get_class($log)); - $this->assertEquals($log, $deserialized); - } - } - - public function testCircularReference() - { - $object = new CircularReferenceParent(); - $this->assertEquals($this->getContent('circular_reference'), $this->serialize($object)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('circular_reference'), get_class($object)); - - $col = $this->getField($deserialized, 'collection'); - $this->assertEquals(2, count($col)); - $this->assertEquals('child1', $col[0]->getName()); - $this->assertEquals('child2', $col[1]->getName()); - $this->assertSame($deserialized, $col[0]->getParent()); - $this->assertSame($deserialized, $col[1]->getParent()); - - $col = $this->getField($deserialized, 'anotherCollection'); - $this->assertEquals(2, count($col)); - $this->assertEquals('child1', $col[0]->getName()); - $this->assertEquals('child2', $col[1]->getName()); - $this->assertSame($deserialized, $col[0]->getParent()); - $this->assertSame($deserialized, $col[1]->getParent()); - } - } - - public function testLifecycleCallbacks() - { - $object = new ObjectWithLifecycleCallbacks(); - $this->assertEquals($this->getContent('lifecycle_callbacks'), $this->serialize($object)); - $this->assertAttributeSame(null, 'name', $object); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($this->getContent('lifecycle_callbacks'), get_class($object)); - $this->assertEquals($object, $deserialized); - } - } - - public function testFormErrors() - { - $errors = array( - new FormError('This is the form error'), - new FormError('Another error') - ); - - $this->assertEquals($this->getContent('form_errors'), $this->serialize($errors)); - } - - public function testNestedFormErrors() - { - $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - - $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('foo', null, $dispatcher); - $formConfigBuilder->setCompound(true); - $formConfigBuilder->setDataMapper($this->getMock('Symfony\Component\Form\DataMapperInterface')); - $fooConfig = $formConfigBuilder->getFormConfig(); - - $form = new Form($fooConfig); - $form->addError(new FormError('This is the form error')); - - $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('bar', null, $dispatcher); - $barConfig = $formConfigBuilder->getFormConfig(); - $child = new Form($barConfig); - $child->addError(new FormError('Error of the child form')); - $form->add($child); - - $this->assertEquals($this->getContent('nested_form_errors'), $this->serialize($form)); - } - - public function testFormErrorsWithNonFormComponents() - { - - if (!class_exists('Symfony\Component\Form\Extension\Core\Type\SubmitType')) { - $this->markTestSkipped('Not using Symfony Form >= 2.3 with submit type'); - } - - $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - - $factoryBuilder = new FormFactoryBuilder(); - $factoryBuilder->addType(new \Symfony\Component\Form\Extension\Core\Type\SubmitType); - $factoryBuilder->addType(new \Symfony\Component\Form\Extension\Core\Type\ButtonType); - $factory = $factoryBuilder->getFormFactory(); - - $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('foo', null, $dispatcher); - $formConfigBuilder->setFormFactory($factory); - $formConfigBuilder->setCompound(true); - $formConfigBuilder->setDataMapper($this->getMock('Symfony\Component\Form\DataMapperInterface')); - $fooConfig = $formConfigBuilder->getFormConfig(); - - $form = new Form($fooConfig); - $form->add('save', 'submit'); - - try { - $this->serialize($form); - } catch (\Exception $e) { - $this->assertTrue(false, 'Serialization should not throw an exception'); - } - } - - public function testConstraintViolation() - { - $violation = new ConstraintViolation('Message of violation', array(), null, 'foo', null); - - $this->assertEquals($this->getContent('constraint_violation'), $this->serialize($violation)); - } - - public function testConstraintViolationList() - { - $violations = new ConstraintViolationList(); - $violations->add(new ConstraintViolation('Message of violation', array(), null, 'foo', null)); - $violations->add(new ConstraintViolation('Message of another violation', array(), null, 'bar', null)); - - $this->assertEquals($this->getContent('constraint_violation_list'), $this->serialize($violations)); - } - - public function testDoctrineProxy() - { - if (!class_exists('Doctrine\ORM\Version')) { - $this->markTestSkipped('Doctrine is not available.'); - } - - $object = new SimpleObjectProxy('foo', 'bar'); - - $this->assertEquals($this->getContent('orm_proxy'), $this->serialize($object)); - } - - public function testInitializedDoctrineProxy() - { - if (!class_exists('Doctrine\ORM\Version')) { - $this->markTestSkipped('Doctrine is not available.'); - } - - $object = new SimpleObjectProxy('foo', 'bar'); - $object->__load(); - - $this->assertEquals($this->getContent('orm_proxy'), $this->serialize($object)); - } - - public function testCustomAccessor() - { - $post = new IndexedCommentsBlogPost(); - - $this->assertEquals($this->getContent('custom_accessor'), $this->serialize($post)); - } - - public function testMixedAccessTypes() - { - $object = new GetSetObject(); - - $this->assertEquals($this->getContent('mixed_access_types'), $this->serialize($object)); - - if ($this->hasDeserializer()) { - $object = $this->deserialize($this->getContent('mixed_access_types'), 'JMS\Serializer\Tests\Fixtures\GetSetObject'); - $this->assertAttributeEquals(1, 'id', $object); - $this->assertAttributeEquals('Johannes', 'name', $object); - $this->assertAttributeEquals(42, 'readOnlyProperty', $object); - } - } - - public function testAccessorOrder() - { - $this->assertEquals($this->getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); - $this->assertEquals($this->getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); - $this->assertEquals($this->getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); - } - - public function testGroups() - { - $groupsObject = new GroupsObject(); - - $this->assertEquals($this->getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); - - $this->assertEquals( - $this->getContent('groups_foo'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('foo'))) - ); - - $this->assertEquals( - $this->getContent('groups_foobar'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('foo', 'bar'))) - ); - - $this->assertEquals( - $this->getContent('groups_all'), - $this->serializer->serialize($groupsObject, $this->getFormat()) - ); - - $this->assertEquals( - $this->getContent('groups_default'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('Default'))) - ); - } - - /** - * @expectedException JMS\Serializer\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups? - */ - public function testInvalidGroupName() - { - $groupsObject = new InvalidGroupsObject(); - - $this->serializer->serialize($groupsObject, $this->getFormat()); - } - - public function testVirtualProperty() - { - $this->assertEquals($this->getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); - } - - public function testVirtualVersions() - { - $this->assertEquals( - $this->getContent('virtual_properties_low'), - $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(2)) - ); - - $this->assertEquals( - $this->getContent('virtual_properties_all'), - $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(7)) - ); - - $this->assertEquals( - $this->getContent('virtual_properties_high'), - $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(9)) - ); - } - - public function testCustomHandler() - { - if ( ! $this->hasDeserializer()) { - return; - } - - $handler = function() { - return new CustomDeserializationObject('customly_unserialized_value'); - }; - - $this->handlerRegistry->registerHandler(GraphNavigator::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); - - $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); - $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); - $this->assertEquals('customly_unserialized_value', $object->someProperty); - } - - public function testInput() - { - $this->assertEquals($this->getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); - } - - public function testObjectWithEmptyHash() - { - $this->assertEquals($this->getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); - } - - /** - * @group null - */ - public function testSerializeObjectWhenNull() - { - $this->assertEquals( - $this->getContent('object_when_null'), - $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)) - ); - - $this->assertEquals( - $this->getContent('object_when_null_and_serialized'), - $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)) - ); - } - - /** - * @group polymorphic - */ - public function testPolymorphicObjects() - { - $this->assertEquals( - $this->getContent('car'), - $this->serialize(new Car(5)) - ); - - if ($this->hasDeserializer()) { - $this->assertEquals( - new Car(5), - $this->deserialize( - $this->getContent('car'), - 'JMS\Serializer\Tests\Fixtures\Discriminator\Car' - ), - 'Class is resolved correctly when concrete sub-class is used.' - ); - - $this->assertEquals( - new Car(5), - $this->deserialize( - $this->getContent('car'), - 'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle' - ), - 'Class is resolved correctly when least supertype is used.' - ); - - $this->assertEquals( - new Car(5), - $this->deserialize( - $this->getContent('car_without_type'), - 'JMS\Serializer\Tests\Fixtures\Discriminator\Car' - ), - 'Class is resolved correctly when concrete sub-class is used and no type is defined.' - ); - } - } - - /** - * @group polymorphic - * @expectedException LogicException - */ - public function testPolymorphicObjectsInvalidDeserialization() - { - if (!$this->hasDeserializer()) { - throw new \LogicException('No deserializer'); - } - - $this->deserialize( - $this->getContent('car_without_type'), - 'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle' - ); - } - - public function testDepthExclusionStrategy() - { - $context = SerializationContext::create() - ->addExclusionStrategy(new DepthExclusionStrategy()) - ; - - $data = new Tree( - new Node(array( - new Node(array( - new Node(array( - new Node(array( - new Node(), - )), - )), - )), - )) - ); - - $this->assertEquals($this->getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); - } - - public function testDeserializingIntoExistingObject() - { - if (!$this->hasDeserializer()) { - return; - } - - $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); - $serializer = new Serializer( - $this->factory, $this->handlerRegistry, $objectConstructor, - $this->serializationVisitors, $this->deserializationVisitors, $this->dispatcher - ); - - $order = new Order(new Price(12)); - - $context = new DeserializationContext(); - $context->attributes->set('target', $order); - - $deseralizedOrder = $serializer->deserialize( - $this->getContent('order'), - get_class($order), - $this->getFormat(), - $context - ); - - $this->assertSame($order, $deseralizedOrder); - $this->assertEquals(new Order(new Price(12.34)), $deseralizedOrder); - $this->assertAttributeInstanceOf('JMS\Serializer\Tests\Fixtures\Price', 'cost', $deseralizedOrder); - } - - abstract protected function getContent($key); - abstract protected function getFormat(); - - protected function hasDeserializer() - { - return true; - } - - protected function serialize($data, Context $context = null) - { - return $this->serializer->serialize($data, $this->getFormat(), $context); - } - - protected function deserialize($content, $type, Context $context = null) - { - return $this->serializer->deserialize($content, $type, $this->getFormat(), $context); - } - protected function setUp() { $this->factory = new MetadataFactory(new AnnotationDriver(new AnnotationReader())); @@ -956,19 +110,4 @@ function(VisitorInterface $visitor, $data, $type, Context $context) { $this->serializer = new Serializer($this->factory, $this->handlerRegistry, $objectConstructor, $this->serializationVisitors, $this->deserializationVisitors, $this->dispatcher); } - - protected function getField($obj, $name) - { - $ref = new \ReflectionProperty($obj, $name); - $ref->setAccessible(true); - - return $ref->getValue($obj); - } - - private function setField($obj, $name, $value) - { - $ref = new \ReflectionProperty($obj, $name); - $ref->setAccessible(true); - $ref->setValue($obj, $value); - } } diff --git a/tests/JMS/Serializer/Tests/Serializer/BaseSerializingTest.php b/tests/JMS/Serializer/Tests/Serializer/BaseSerializingTest.php new file mode 100644 index 000000000..f9e1de120 --- /dev/null +++ b/tests/JMS/Serializer/Tests/Serializer/BaseSerializingTest.php @@ -0,0 +1,900 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace JMS\Serializer\Tests\Serializer; + +use JMS\Serializer\Context; +use JMS\Serializer\DeserializationContext; +use JMS\Serializer\GraphNavigator; +use JMS\Serializer\SerializationContext; +use JMS\Serializer\Tests\Fixtures\DateTimeArraysObject; +use JMS\Serializer\Tests\Fixtures\Discriminator\Car; +use JMS\Serializer\Tests\Fixtures\InlineChildEmpty; +use JMS\Serializer\Tests\Fixtures\NamedDateTimeArraysObject; +use JMS\Serializer\Tests\Fixtures\Tree; +use PhpCollection\Sequence; +use Symfony\Component\Form\FormFactoryBuilder; +use JMS\Serializer\Handler\HandlerRegistry; +use JMS\Serializer\EventDispatcher\EventDispatcher; +use Doctrine\Common\Collections\ArrayCollection; +use JMS\Serializer\Construction\UnserializeObjectConstructor; +use JMS\Serializer\Serializer; +use JMS\Serializer\Tests\Fixtures\AccessorOrderChild; +use JMS\Serializer\Tests\Fixtures\AccessorOrderParent; +use JMS\Serializer\Tests\Fixtures\AccessorOrderMethod; +use JMS\Serializer\Tests\Fixtures\Author; +use JMS\Serializer\Tests\Fixtures\Publisher; +use JMS\Serializer\Tests\Fixtures\AuthorReadOnly; +use JMS\Serializer\Tests\Fixtures\BlogPost; +use JMS\Serializer\Tests\Fixtures\CircularReferenceParent; +use JMS\Serializer\Tests\Fixtures\Comment; +use JMS\Serializer\Tests\Fixtures\CurrencyAwareOrder; +use JMS\Serializer\Tests\Fixtures\CurrencyAwarePrice; +use JMS\Serializer\Tests\Fixtures\CustomDeserializationObject; +use JMS\Serializer\Tests\Fixtures\GetSetObject; +use JMS\Serializer\Tests\Fixtures\GroupsObject; +use JMS\Serializer\Tests\Fixtures\InvalidGroupsObject; +use JMS\Serializer\Tests\Fixtures\IndexedCommentsBlogPost; +use JMS\Serializer\Tests\Fixtures\InlineParent; +use JMS\Serializer\Tests\Fixtures\InitializedObjectConstructor; +use JMS\Serializer\Tests\Fixtures\InitializedBlogPostConstructor; +use JMS\Serializer\Tests\Fixtures\Log; +use JMS\Serializer\Tests\Fixtures\ObjectWithLifecycleCallbacks; +use JMS\Serializer\Tests\Fixtures\ObjectWithVersionedVirtualProperties; +use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualProperties; +use JMS\Serializer\Tests\Fixtures\Order; +use JMS\Serializer\Tests\Fixtures\Price; +use JMS\Serializer\Tests\Fixtures\SimpleObject; +use JMS\Serializer\Tests\Fixtures\ObjectWithNullProperty; +use JMS\Serializer\Tests\Fixtures\SimpleObjectProxy; +use JMS\Serializer\Tests\Fixtures\Article; +use JMS\Serializer\Tests\Fixtures\Input; +use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyHash; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormError; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use JMS\Serializer\Exclusion\DepthExclusionStrategy; +use JMS\Serializer\Tests\Fixtures\Node; +use JMS\Serializer\Tests\Fixtures\AuthorReadOnlyPerClass; + +abstract class BaseSerializingTest extends BaseSerializationTest +{ + protected $factory; + protected $dispatcher; + + /** @var Serializer */ + protected $serializer; + protected $handlerRegistry; + protected $serializationVisitors; + protected $deserializationVisitors; + + public function testSerializeNullArray() + { + $arr = array('foo' => 'bar', 'baz' => null, null); + + $this->assertEquals( + $this->getContent('nullable'), + $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)) + ); + } + + public function testSerializeNullObject() + { + $obj = new ObjectWithNullProperty('foo', 'bar'); + + $this->assertEquals( + $this->getContent('simple_object_nullable'), + $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)) + ); + } + + /** + * @dataProvider getTypes + */ + public function testNull($type) + { + $this->assertEquals($this->getContent('null'), $this->serialize(null), $type); + + if ($this->hasDeserializer()) { + $this->assertEquals(null, $this->deserialize($this->getContent('null'), $type)); + } + } + + public function getTypes() + { + return array( + array('NULL'), + array('integer'), + array('double'), + array('float'), + array('string'), + array('DateTime'), + ); + } + + public function testString() + { + $this->assertEquals($this->getContent('string'), $this->serialize('foo')); + + if ($this->hasDeserializer()) { + $this->assertEquals('foo', $this->deserialize($this->getContent('string'), 'string')); + } + } + + /** + * @dataProvider getBooleans + */ + public function testBooleans($strBoolean, $boolean) + { + $this->assertEquals($this->getContent('boolean_'.$strBoolean), $this->serialize($boolean)); + + if ($this->hasDeserializer()) { + $this->assertSame($boolean, $this->deserialize($this->getContent('boolean_'.$strBoolean), 'boolean')); + } + } + + public function getBooleans() + { + return array(array('true', true), array('false', false)); + } + + /** + * @dataProvider getNumerics + */ + public function testNumerics($key, $value, $type) + { + $this->assertEquals($this->getContent($key), $this->serialize($value)); + + if ($this->hasDeserializer()) { + $this->assertEquals($value, $this->deserialize($this->getContent($key), $type)); + } + } + + public function getNumerics() + { + return array( + array('integer', 1, 'integer'), + array('float', 4.533, 'double'), + array('float', 4.533, 'float'), + array('float_trailing_zero', 1.0, 'double'), + array('float_trailing_zero', 1.0, 'float'), + ); + } + + public function testSimpleObject() + { + $this->assertEquals($this->getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); + + if ($this->hasDeserializer()) { + $this->assertEquals($obj, $this->deserialize($this->getContent('simple_object'), get_class($obj))); + } + } + + public function testArrayStrings() + { + $data = array('foo', 'bar'); + $this->assertEquals($this->getContent('array_strings'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + $this->assertEquals($data, $this->deserialize($this->getContent('array_strings'), 'array')); + } + } + + public function testArrayBooleans() + { + $data = array(true, false); + $this->assertEquals($this->getContent('array_booleans'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + $this->assertEquals($data, $this->deserialize($this->getContent('array_booleans'), 'array')); + } + } + + public function testArrayIntegers() + { + $data = array(1, 3, 4); + $this->assertEquals($this->getContent('array_integers'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + $this->assertEquals($data, $this->deserialize($this->getContent('array_integers'), 'array')); + } + } + + public function testArrayFloats() + { + $data = array(1.34, 3.0, 6.42); + $this->assertEquals($this->getContent('array_floats'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + $this->assertEquals($data, $this->deserialize($this->getContent('array_floats'), 'array')); + } + } + + public function testArrayObjects() + { + $data = array(new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')); + $this->assertEquals($this->getContent('array_objects'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + $this->assertEquals($data, $this->deserialize($this->getContent('array_objects'), 'array')); + } + } + + + public function testDateTimeArrays() + { + $data = array( + new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + new \DateTime('2013-12-05 00:00:00', new \DateTimeZone('UTC')) + ); + + $object = new DateTimeArraysObject($data, $data); + $serializedObject = $this->serialize( $object ); + + $this->assertEquals($this->getContent('array_datetimes_object'), $serializedObject); + + if ($this->hasDeserializer()) { + /** @var DateTimeArraysObject $deserializedObject */ + $deserializedObject = $this->deserialize($this->getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); + + /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */ + foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + $this->assertEquals($object, $deserializedObject); + } + } + + public function testNamedDateTimeArrays() + { + $data = array( + new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + new \DateTime('2013-12-05 00:00:00', new \DateTimeZone('UTC')) + ); + + $object = new NamedDateTimeArraysObject(array('testdate1' => $data[0], 'testdate2' => $data[1])); + $serializedObject = $this->serialize( $object ); + + $this->assertEquals($this->getContent('array_named_datetimes_object'), $serializedObject); + + if ($this->hasDeserializer()) { + + // skip XML deserialization + if ($this->getFormat() === 'xml') { + return; + } + + /** @var NamedDateTimeArraysObject $deserializedObject */ + $deserializedObject = $this->deserialize($this->getContent('array_named_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\NamedDateTimeArraysObject'); + + /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */ + foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + $this->assertEquals($object, $deserializedObject); + } + } + + + public function testArrayMixed() + { + $this->assertEquals($this->getContent('array_mixed'), $this->serialize(array('foo', 1, true, new SimpleObject('foo', 'bar'), array(1, 3, true)))); + } + + /** + * @dataProvider getDateTime + * @group datetime + */ + public function testDateTime($key, $value, $type) + { + $this->assertEquals($this->getContent($key), $this->serialize($value)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent($key), $type); + + $this->assertTrue(is_object($deserialized)); + $this->assertEquals(get_class($value), get_class($deserialized)); + $this->assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); + } + } + + public function getDateTime() + { + return array( + array('date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'), + ); + } + + public function testDateInterval() + { + $duration = new \DateInterval('PT45M'); + + $this->assertEquals($this->getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); + } + + public function testBlogPost() + { + $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo')); + $post->addComment($comment = new Comment($author, 'foo')); + + $this->assertEquals($this->getContent('blog_post'), $this->serialize($post)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('blog_post'), get_class($post)); + $this->assertEquals('2011-07-30T00:00:00+0000', $this->getField($deserialized, 'createdAt')->format(\DateTime::ISO8601)); + $this->assertAttributeEquals('This is a nice title.', 'title', $deserialized); + $this->assertAttributeSame(false, 'published', $deserialized); + $this->assertAttributeSame('1edf9bf60a32d89afbb85b2be849e3ceed5f5b10', 'etag', $deserialized); + $this->assertAttributeEquals(new ArrayCollection(array($comment)), 'comments', $deserialized); + $this->assertAttributeEquals(new Sequence(array($comment)), 'comments2', $deserialized); + $this->assertAttributeEquals($author, 'author', $deserialized); + } + } + + public function testDeserializingNull() + { + $objectConstructor = new InitializedBlogPostConstructor(); + $this->serializer = new Serializer($this->factory, $this->handlerRegistry, $objectConstructor, $this->serializationVisitors, $this->deserializationVisitors, $this->dispatcher); + + $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo')); + + $this->setField($post, 'author', null); + $this->setField($post, 'publisher', null); + + $this->assertEquals($this->getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()->setSerializeNull(true)); + + $this->assertEquals('2011-07-30T00:00:00+0000', $this->getField($deserialized, 'createdAt')->format(\DateTime::ISO8601)); + $this->assertAttributeEquals('This is a nice title.', 'title', $deserialized); + $this->assertAttributeSame(false, 'published', $deserialized); + $this->assertAttributeEquals(new ArrayCollection(), 'comments', $deserialized); + $this->assertEquals(null, $this->getField($deserialized, 'author')); + } + } + + public function testReadOnly() + { + $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); + $this->assertEquals($this->getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author)); + $this->assertNull($this->getField($deserialized, 'id')); + $this->assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testReadOnlyClass() + { + $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); + $this->assertEquals($this->getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author)); + $this->assertNull($this->getField($deserialized, 'id')); + $this->assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testPrice() + { + $price = new Price(3); + $this->assertEquals($this->getContent('price'), $this->serialize($price)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('price'), get_class($price)); + $this->assertEquals(3, $this->getField($deserialized, 'price')); + } + } + + public function testOrder() + { + $order = new Order(new Price(12.34)); + $this->assertEquals($this->getContent('order'), $this->serialize($order)); + + if ($this->hasDeserializer()) { + $this->assertEquals($order, $this->deserialize($this->getContent('order'), get_class($order))); + } + } + + public function testCurrencyAwarePrice() + { + $price = new CurrencyAwarePrice(2.34); + $this->assertEquals($this->getContent('currency_aware_price'), $this->serialize($price)); + + if ($this->hasDeserializer()) { + $this->assertEquals($price, $this->deserialize($this->getContent('currency_aware_price'), get_class($price))); + } + } + + public function testOrderWithCurrencyAwarePrice() + { + $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); + $this->assertEquals($this->getContent('order_with_currency_aware_price'), $this->serialize($order)); + + if ($this->hasDeserializer()) { + $this->assertEquals($order, $this->deserialize($this->getContent('order_with_currency_aware_price'), get_class($order))); + } + } + + /** + * @group handlerCallback + */ + public function testArticle() + { + $article = new Article(); + $article->element = 'custom'; + $article->value = 'serialized'; + + $result = $this->serialize($article); + $this->assertEquals($this->getContent('article'), $result); + + if ($this->hasDeserializer()) { + $this->assertEquals($article, $this->deserialize($result, 'JMS\Serializer\Tests\Fixtures\Article')); + } + } + + public function testInline() + { + $inline = new InlineParent(); + + $result = $this->serialize($inline); + $this->assertEquals($this->getContent('inline'), $result); + + // no deserialization support + } + + public function testInlineEmptyChild() + { + $inline = new InlineParent(new InlineChildEmpty()); + + $result = $this->serialize($inline); + $this->assertEquals($this->getContent('inline_child_empty'), $result); + + // no deserialization support + } + + /** + * @group log + */ + public function testLog() + { + $this->assertEquals($this->getContent('log'), $this->serialize($log = new Log())); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('log'), get_class($log)); + $this->assertEquals($log, $deserialized); + } + } + + public function testCircularReference() + { + $object = new CircularReferenceParent(); + $this->assertEquals($this->getContent('circular_reference'), $this->serialize($object)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('circular_reference'), get_class($object)); + + $col = $this->getField($deserialized, 'collection'); + $this->assertEquals(2, count($col)); + $this->assertEquals('child1', $col[0]->getName()); + $this->assertEquals('child2', $col[1]->getName()); + $this->assertSame($deserialized, $col[0]->getParent()); + $this->assertSame($deserialized, $col[1]->getParent()); + + $col = $this->getField($deserialized, 'anotherCollection'); + $this->assertEquals(2, count($col)); + $this->assertEquals('child1', $col[0]->getName()); + $this->assertEquals('child2', $col[1]->getName()); + $this->assertSame($deserialized, $col[0]->getParent()); + $this->assertSame($deserialized, $col[1]->getParent()); + } + } + + public function testLifecycleCallbacks() + { + $object = new ObjectWithLifecycleCallbacks(); + $this->assertEquals($this->getContent('lifecycle_callbacks'), $this->serialize($object)); + $this->assertAttributeSame(null, 'name', $object); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($this->getContent('lifecycle_callbacks'), get_class($object)); + $this->assertEquals($object, $deserialized); + } + } + + public function testFormErrors() + { + $errors = array( + new FormError('This is the form error'), + new FormError('Another error') + ); + + $this->assertEquals($this->getContent('form_errors'), $this->serialize($errors)); + } + + public function testNestedFormErrors() + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + + $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('foo', null, $dispatcher); + $formConfigBuilder->setCompound(true); + $formConfigBuilder->setDataMapper($this->getMock('Symfony\Component\Form\DataMapperInterface')); + $fooConfig = $formConfigBuilder->getFormConfig(); + + $form = new Form($fooConfig); + $form->addError(new FormError('This is the form error')); + + $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('bar', null, $dispatcher); + $barConfig = $formConfigBuilder->getFormConfig(); + $child = new Form($barConfig); + $child->addError(new FormError('Error of the child form')); + $form->add($child); + + $this->assertEquals($this->getContent('nested_form_errors'), $this->serialize($form)); + } + + public function testFormErrorsWithNonFormComponents() + { + + if (!class_exists('Symfony\Component\Form\Extension\Core\Type\SubmitType')) { + $this->markTestSkipped('Not using Symfony Form >= 2.3 with submit type'); + } + + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + + $factoryBuilder = new FormFactoryBuilder(); + $factoryBuilder->addType(new \Symfony\Component\Form\Extension\Core\Type\SubmitType); + $factoryBuilder->addType(new \Symfony\Component\Form\Extension\Core\Type\ButtonType); + $factory = $factoryBuilder->getFormFactory(); + + $formConfigBuilder = new \Symfony\Component\Form\FormConfigBuilder('foo', null, $dispatcher); + $formConfigBuilder->setFormFactory($factory); + $formConfigBuilder->setCompound(true); + $formConfigBuilder->setDataMapper($this->getMock('Symfony\Component\Form\DataMapperInterface')); + $fooConfig = $formConfigBuilder->getFormConfig(); + + $form = new Form($fooConfig); + $form->add('save', 'submit'); + + try { + $this->serialize($form); + } catch (\Exception $e) { + $this->assertTrue(false, 'Serialization should not throw an exception'); + } + } + + public function testConstraintViolation() + { + $violation = new ConstraintViolation('Message of violation', array(), null, 'foo', null); + + $this->assertEquals($this->getContent('constraint_violation'), $this->serialize($violation)); + } + + public function testConstraintViolationList() + { + $violations = new ConstraintViolationList(); + $violations->add(new ConstraintViolation('Message of violation', array(), null, 'foo', null)); + $violations->add(new ConstraintViolation('Message of another violation', array(), null, 'bar', null)); + + $this->assertEquals($this->getContent('constraint_violation_list'), $this->serialize($violations)); + } + + public function testDoctrineProxy() + { + if (!class_exists('Doctrine\ORM\Version')) { + $this->markTestSkipped('Doctrine is not available.'); + } + + $object = new SimpleObjectProxy('foo', 'bar'); + + $this->assertEquals($this->getContent('orm_proxy'), $this->serialize($object)); + } + + public function testInitializedDoctrineProxy() + { + if (!class_exists('Doctrine\ORM\Version')) { + $this->markTestSkipped('Doctrine is not available.'); + } + + $object = new SimpleObjectProxy('foo', 'bar'); + $object->__load(); + + $this->assertEquals($this->getContent('orm_proxy'), $this->serialize($object)); + } + + public function testCustomAccessor() + { + $post = new IndexedCommentsBlogPost(); + + $this->assertEquals($this->getContent('custom_accessor'), $this->serialize($post)); + } + + public function testMixedAccessTypes() + { + $object = new GetSetObject(); + + $this->assertEquals($this->getContent('mixed_access_types'), $this->serialize($object)); + + if ($this->hasDeserializer()) { + $object = $this->deserialize($this->getContent('mixed_access_types'), 'JMS\Serializer\Tests\Fixtures\GetSetObject'); + $this->assertAttributeEquals(1, 'id', $object); + $this->assertAttributeEquals('Johannes', 'name', $object); + $this->assertAttributeEquals(42, 'readOnlyProperty', $object); + } + } + + public function testAccessorOrder() + { + $this->assertEquals($this->getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); + $this->assertEquals($this->getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); + $this->assertEquals($this->getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); + } + + public function testGroups() + { + $groupsObject = new GroupsObject(); + + $this->assertEquals($this->getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); + + $this->assertEquals( + $this->getContent('groups_foo'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('foo'))) + ); + + $this->assertEquals( + $this->getContent('groups_foobar'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('foo', 'bar'))) + ); + + $this->assertEquals( + $this->getContent('groups_all'), + $this->serializer->serialize($groupsObject, $this->getFormat()) + ); + + $this->assertEquals( + $this->getContent('groups_default'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(array('Default'))) + ); + } + + /** + * @expectedException JMS\Serializer\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups? + */ + public function testInvalidGroupName() + { + $groupsObject = new InvalidGroupsObject(); + + $this->serializer->serialize($groupsObject, $this->getFormat()); + } + + public function testVirtualProperty() + { + $this->assertEquals($this->getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); + } + + public function testVirtualVersions() + { + $this->assertEquals( + $this->getContent('virtual_properties_low'), + $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(2)) + ); + + $this->assertEquals( + $this->getContent('virtual_properties_all'), + $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(7)) + ); + + $this->assertEquals( + $this->getContent('virtual_properties_high'), + $this->serialize(new ObjectWithVersionedVirtualProperties(), SerializationContext::create()->setVersion(9)) + ); + } + + public function testCustomHandler() + { + if ( ! $this->hasDeserializer()) { + return; + } + + $handler = function() { + return new CustomDeserializationObject('customly_unserialized_value'); + }; + + $this->handlerRegistry->registerHandler(GraphNavigator::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); + + $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); + $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); + $this->assertEquals('customly_unserialized_value', $object->someProperty); + } + + public function testInput() + { + $this->assertEquals($this->getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); + } + + public function testObjectWithEmptyHash() + { + $this->assertEquals($this->getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); + } + + /** + * @group null + */ + public function testSerializeObjectWhenNull() + { + $this->assertEquals( + $this->getContent('object_when_null'), + $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)) + ); + + $this->assertEquals( + $this->getContent('object_when_null_and_serialized'), + $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)) + ); + } + + /** + * @group polymorphic + */ + public function testPolymorphicObjects() + { + $this->assertEquals( + $this->getContent('car'), + $this->serialize(new Car(5)) + ); + + if ($this->hasDeserializer()) { + $this->assertEquals( + new Car(5), + $this->deserialize( + $this->getContent('car'), + 'JMS\Serializer\Tests\Fixtures\Discriminator\Car' + ), + 'Class is resolved correctly when concrete sub-class is used.' + ); + + $this->assertEquals( + new Car(5), + $this->deserialize( + $this->getContent('car'), + 'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle' + ), + 'Class is resolved correctly when least supertype is used.' + ); + + $this->assertEquals( + new Car(5), + $this->deserialize( + $this->getContent('car_without_type'), + 'JMS\Serializer\Tests\Fixtures\Discriminator\Car' + ), + 'Class is resolved correctly when concrete sub-class is used and no type is defined.' + ); + } + } + + /** + * @group polymorphic + * @expectedException LogicException + */ + public function testPolymorphicObjectsInvalidDeserialization() + { + if (!$this->hasDeserializer()) { + throw new \LogicException('No deserializer'); + } + + $this->deserialize( + $this->getContent('car_without_type'), + 'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle' + ); + } + + public function testDepthExclusionStrategy() + { + $context = SerializationContext::create() + ->addExclusionStrategy(new DepthExclusionStrategy()) + ; + + $data = new Tree( + new Node(array( + new Node(array( + new Node(array( + new Node(array( + new Node(), + )), + )), + )), + )) + ); + + $this->assertEquals($this->getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); + } + + public function testDeserializingIntoExistingObject() + { + if (!$this->hasDeserializer()) { + return; + } + + $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); + $serializer = new Serializer( + $this->factory, $this->handlerRegistry, $objectConstructor, + $this->serializationVisitors, $this->deserializationVisitors, $this->dispatcher + ); + + $order = new Order(new Price(12)); + + $context = new DeserializationContext(); + $context->attributes->set('target', $order); + + $deseralizedOrder = $serializer->deserialize( + $this->getContent('order'), + get_class($order), + $this->getFormat(), + $context + ); + + $this->assertSame($order, $deseralizedOrder); + $this->assertEquals(new Order(new Price(12.34)), $deseralizedOrder); + $this->assertAttributeInstanceOf('JMS\Serializer\Tests\Fixtures\Price', 'cost', $deseralizedOrder); + } + + abstract protected function getContent($key); + abstract protected function getFormat(); + + protected function hasDeserializer() + { + return true; + } + + protected function serialize($data, Context $context = null) + { + return $this->serializer->serialize($data, $this->getFormat(), $context); + } + + protected function deserialize($content, $type, Context $context = null) + { + return $this->serializer->deserialize($content, $type, $this->getFormat(), $context); + } + + protected function getField($obj, $name) + { + $ref = new \ReflectionProperty($obj, $name); + $ref->setAccessible(true); + + return $ref->getValue($obj); + } + + private function setField($obj, $name, $value) + { + $ref = new \ReflectionProperty($obj, $name); + $ref->setAccessible(true); + $ref->setValue($obj, $value); + } +} diff --git a/tests/JMS/Serializer/Tests/Serializer/JsonSerializationTest.php b/tests/JMS/Serializer/Tests/Serializer/JsonSerializationTest.php index 9d74724eb..873abb7e3 100644 --- a/tests/JMS/Serializer/Tests/Serializer/JsonSerializationTest.php +++ b/tests/JMS/Serializer/Tests/Serializer/JsonSerializationTest.php @@ -27,7 +27,7 @@ use JMS\Serializer\Tests\Fixtures\Author; use JMS\Serializer\Tests\Fixtures\AuthorList; -class JsonSerializationTest extends BaseSerializationTest +class JsonSerializationTest extends BaseSerializingTest { protected function getContent($key) { diff --git a/tests/JMS/Serializer/Tests/Serializer/NestedSerializationTest.php b/tests/JMS/Serializer/Tests/Serializer/NestedSerializationTest.php new file mode 100644 index 000000000..b8e0db061 --- /dev/null +++ b/tests/JMS/Serializer/Tests/Serializer/NestedSerializationTest.php @@ -0,0 +1,75 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use JMS\Serializer\EventDispatcher\Event; +use JMS\Serializer\EventDispatcher\EventDispatcher; +use JMS\Serializer\EventDispatcher\EventSubscriberInterface; +use JMS\Serializer\Serializer; +use JMS\Serializer\Tests\Fixtures\Author; +use JMS\Serializer\Tests\Fixtures\AuthorList; + +class NestedSerializationTest extends BaseSerializationTest +{ + protected function setUp() + { + parent::setup(); + $this->dispatcher->addSubscriber(new CallingSerializerSubscriber($this->serializer)); + } + + public function testSerializerImbrication() + { + $list = new AuthorList(); + $list->add(new Author('foo')); + $list->add(new Author('bar')); + + $this->assertEquals('{"authors":[{"full_name":"foo"},{"full_name":"bar"}],"_extraJson":"{\"foo\":\"bar\"}"}', $this->serializer->serialize($list, 'json')); + } + + protected function getContent($key) + { + return []; + } + + protected function getFormat() + { + return 'json'; + } +} + +class CallingSerializerSubscriber implements EventSubscriberInterface +{ + protected $serializer; + + public function __construct($serializer) + { + $this->serializer = $serializer; + } + + public function onPostSerialize(Event $event) + { + // could be to add data, or log, send message etc... + $event->getVisitor()->addData('_extraJson', $this->serializer->serialize(array('foo' => 'bar'), 'json')); + } + + public static function getSubscribedEvents() + { + return array( + array('event' => 'serializer.post_serialize', 'method' => 'onPostSerialize', 'format' => 'json', 'class' => 'JMS\Serializer\Tests\Fixtures\AuthorList'), + ); + } +} diff --git a/tests/JMS/Serializer/Tests/Serializer/XmlSerializationTest.php b/tests/JMS/Serializer/Tests/Serializer/XmlSerializationTest.php index 0a66739f7..27ab32915 100644 --- a/tests/JMS/Serializer/Tests/Serializer/XmlSerializationTest.php +++ b/tests/JMS/Serializer/Tests/Serializer/XmlSerializationTest.php @@ -36,7 +36,7 @@ use JMS\Serializer\Tests\Fixtures\SimpleClassObject; use JMS\Serializer\Tests\Fixtures\SimpleSubClassObject; -class XmlSerializationTest extends BaseSerializationTest +class XmlSerializationTest extends BaseSerializingTest { /** * @expectedException JMS\Serializer\Exception\RuntimeException diff --git a/tests/JMS/Serializer/Tests/Serializer/YamlSerializationTest.php b/tests/JMS/Serializer/Tests/Serializer/YamlSerializationTest.php index 03a4d76c3..1c47d1845 100644 --- a/tests/JMS/Serializer/Tests/Serializer/YamlSerializationTest.php +++ b/tests/JMS/Serializer/Tests/Serializer/YamlSerializationTest.php @@ -20,7 +20,7 @@ use JMS\Serializer\Exception\RuntimeException; -class YamlSerializationTest extends BaseSerializationTest +class YamlSerializationTest extends BaseSerializingTest { public function testConstraintViolation() {