Browse Source

Merge branch 'ormProxy'

Johannes Schmitt 13 years ago
parent
commit
559c295b45

+ 33 - 0
DependencyInjection/Factory/DoctrineOrmProxyFactory.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace JMS\SerializerBundle\DependencyInjection\Factory;
+
+use JMS\SerializerBundle\DependencyInjection\HandlerFactoryInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+
+class DoctrineOrmProxyFactory implements HandlerFactoryInterface
+{
+    public function getConfigKey()
+    {
+        return 'doctrine_orm';
+    }
+
+    public function getType(array $config)
+    {
+        return self::TYPE_SERIALIZATION;
+    }
+
+    public function addConfiguration(ArrayNodeDefinition $builder)
+    {
+        $builder
+            ->addDefaultsIfNotSet()
+            ->defaultValue(array())
+        ;
+    }
+
+    public function getHandlerId(ContainerBuilder $container, array $config)
+    {
+        return 'jms_serializer.doctrine_orm_handler';
+    }
+}

+ 2 - 0
JMSSerializerBundle.php

@@ -23,6 +23,7 @@ use JMS\SerializerBundle\DependencyInjection\Factory\DateTimeFactory;
 use JMS\SerializerBundle\DependencyInjection\Factory\ConstraintViolationFactory;
 use JMS\SerializerBundle\DependencyInjection\Factory\ArrayCollectionFactory;
 use JMS\SerializerBundle\DependencyInjection\Factory\ObjectBasedFactory;
+use JMS\SerializerBundle\DependencyInjection\Factory\DoctrineOrmProxyFactory;
 use JMS\SerializerBundle\DependencyInjection\JMSSerializerExtension;
 use Symfony\Component\HttpKernel\KernelInterface;
 use JMS\SerializerBundle\DependencyInjection\Compiler\SetVisitorsPass;
@@ -46,6 +47,7 @@ class JMSSerializerBundle extends Bundle
     public function configureSerializerExtension(JMSSerializerExtension $ext)
     {
         $ext->addHandlerFactory(new ObjectBasedFactory());
+        $ext->addHandlerFactory(new DoctrineOrmProxyFactory());
         $ext->addHandlerFactory(new ArrayCollectionFactory());
         $ext->addHandlerFactory(new ConstraintViolationFactory());
         $ext->addHandlerFactory(new DateTimeFactory());

+ 2 - 0
Resources/config/services.xml

@@ -37,6 +37,7 @@
         <parameter key="jms_serializer.array_collection_handler.class">JMS\SerializerBundle\Serializer\Handler\ArrayCollectionHandler</parameter>
         <parameter key="jms_serializer.form_error_handler.class">JMS\SerializerBundle\Serializer\Handler\FormErrorHandler</parameter>
         <parameter key="jms_serializer.constraint_violation_handler.class">JMS\SerializerBundle\Serializer\Handler\ConstraintViolationHandler</parameter>
+        <parameter key="jms_serializer.doctrine_orm_handler.class">JMS\SerializerBundle\Serializer\Handler\DoctrineOrmProxyHandler</parameter>
     </parameters>
 
     <services>
@@ -154,5 +155,6 @@
             <argument type="service" id="translator" />
         </service>
         <service id="jms_serializer.constraint_violation_handler" class="%jms_serializer.constraint_violation_handler.class%" public="false" />
+        <service id="jms_serializer.doctrine_orm_handler" class="%jms_serializer.doctrine_orm_handler.class%" public="false" />
     </services>
 </container>

+ 35 - 18
Serializer/GraphNavigator.php

@@ -20,16 +20,22 @@ namespace JMS\SerializerBundle\Serializer;
 
 use JMS\SerializerBundle\Metadata\ClassMetadata;
 use Metadata\MetadataFactoryInterface;
+use JMS\SerializerBundle\Exception\InvalidArgumentException;
 use JMS\SerializerBundle\Serializer\Exclusion\ExclusionStrategyInterface;
 
 final class GraphNavigator
 {
+    const DIRECTION_SERIALIZATION = 1;
+    const DIRECTION_DESERIALIZATION = 2;
+
+    private $direction;
     private $exclusionStrategy;
     private $metadataFactory;
     private $visiting;
 
-    public function __construct(MetadataFactoryInterface $metadataFactory, ExclusionStrategyInterface $exclusionStrategy = null)
+    public function __construct($direction, MetadataFactoryInterface $metadataFactory, ExclusionStrategyInterface $exclusionStrategy = null)
     {
+        $this->direction = $direction;
         $this->metadataFactory = $metadataFactory;
         $this->exclusionStrategy = $exclusionStrategy;
         $this->visiting = new \SplObjectStorage();
@@ -38,7 +44,7 @@ final class GraphNavigator
     public function accept($data, $type, VisitorInterface $visitor)
     {
         // determine type if not given
-        if ($isSerialization = (null === $type)) {
+        if (null === $type) {
             if (null === $data) {
                 return null;
             }
@@ -57,10 +63,10 @@ final class GraphNavigator
             return $visitor->visitBoolean($data, $type);
         } else if ('double' === $type) {
             return $visitor->visitDouble($data, $type);
-        } else if ('array' === $type || 0 === strpos($type, 'array<')) {
+        } else if ('array' === $type || ('a' === $type[0] && 0 === strpos($type, 'array<'))) {
             return $visitor->visitArray($data, $type);
         } else {
-            if ($isSerialization && null !== $data) {
+            if (self::DIRECTION_SERIALIZATION === $this->direction && null !== $data) {
                 if ($this->visiting->contains($data)) {
                     return null;
                 }
@@ -71,7 +77,7 @@ final class GraphNavigator
             $handled = false;
             $rs = $visitor->visitUsingCustomHandler($data, $type, $handled);
             if ($handled) {
-                if ($isSerialization) {
+                if (self::DIRECTION_SERIALIZATION === $this->direction) {
                     $this->visiting->detach($data);
                 }
 
@@ -80,7 +86,7 @@ final class GraphNavigator
 
             $metadata = $this->metadataFactory->getMetadataForClass($type);
             if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata)) {
-                if ($isSerialization) {
+                if (self::DIRECTION_SERIALIZATION === $this->direction) {
                     $this->visiting->detach($data);
                 }
 
@@ -88,16 +94,16 @@ final class GraphNavigator
             }
 
             // pre-serialization callbacks
-            if ($isSerialization) {
+            if (self::DIRECTION_SERIALIZATION === $this->direction) {
                 foreach ($metadata->preSerializeMethods as $method) {
                     $method->invoke($data);
                 }
             }
 
             // check if traversable
-            if ($isSerialization && $data instanceof \Traversable) {
+            if (self::DIRECTION_SERIALIZATION === $this->direction && $data instanceof \Traversable) {
                 $rs = $visitor->visitTraversable($data, $type);
-                $this->afterVisitingObject($metadata, $data, $isSerialization);
+                $this->afterVisitingObject($metadata, $data, self::DIRECTION_SERIALIZATION === $this->direction);
 
                 return $rs;
             }
@@ -115,26 +121,37 @@ final class GraphNavigator
             }
 
             $rs = $visitor->endVisitingObject($metadata, $data, $type);
-            $this->afterVisitingObject($metadata, $isSerialization ? $data : $rs, $isSerialization);
+            $this->afterVisitingObject($metadata, self::DIRECTION_SERIALIZATION === $this->direction ? $data : $rs);
 
             return $rs;
         }
     }
 
-    private function afterVisitingObject(ClassMetadata $metadata, $object, $isSerialization)
+    public function detachObject($object)
     {
-        if ($isSerialization) {
-            $this->visiting->detach($object);
+        if (null === $object) {
+            throw new InvalidArgumentException('$object cannot be null');
+        } else if (!is_object($object)) {
+            throw new InvalidArgumentException(sprintf('Expected an object to detach, given "%s".', gettype($object)));
         }
 
-        if ($isSerialization) {
+        $this->visiting->detach($object);
+    }
+
+    private function afterVisitingObject(ClassMetadata $metadata, $object)
+    {
+        if (self::DIRECTION_SERIALIZATION === $this->direction) {
+            $this->visiting->detach($object);
+
             foreach ($metadata->postSerializeMethods as $method) {
                 $method->invoke($object);
             }
-        } else {
-            foreach ($metadata->postDeserializeMethods as $method) {
-                $method->invoke($object);
-            }
+
+            return;
+        }
+
+        foreach ($metadata->postDeserializeMethods as $method) {
+            $method->invoke($object);
         }
     }
 }

+ 41 - 0
Serializer/Handler/DoctrineOrmProxyHandler.php

@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
+ *
+ * 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\SerializerBundle\Serializer\Handler;
+
+use Doctrine\ORM\Proxy\Proxy;
+use JMS\SerializerBundle\Serializer\VisitorInterface;
+use JMS\SerializerBundle\Serializer\Handler\SerializationHandlerInterface;
+
+class DoctrineOrmProxyHandler implements SerializationHandlerInterface
+{
+    public function serialize(VisitorInterface $visitor, $data, $type, &$handled)
+    {
+        if ($data instanceof Proxy && !$data->__isInitialized__) {
+            $handled = true;
+
+            $data->__load();
+            $visitor->getNavigator()->detachObject($data);
+
+            // pass the parent class not to load the metadata for the proxy class
+            return $visitor->getNavigator()->accept($data, get_parent_class($data), $visitor);
+        }
+
+        return null;
+    }
+}

+ 2 - 2
Serializer/Serializer.php

@@ -57,7 +57,7 @@ class Serializer implements SerializerInterface
     public function serialize($data, $format)
     {
         $visitor = $this->getSerializationVisitor($format);
-        $visitor->setNavigator($navigator = new GraphNavigator($this->factory, $this->exclusionStrategy));
+        $visitor->setNavigator($navigator = new GraphNavigator(GraphNavigator::DIRECTION_SERIALIZATION, $this->factory, $this->exclusionStrategy));
         $navigator->accept($visitor->prepare($data), null, $visitor);
 
         return $visitor->getResult();
@@ -66,7 +66,7 @@ class Serializer implements SerializerInterface
     public function deserialize($data, $type, $format)
     {
         $visitor = $this->getDeserializationVisitor($format);
-        $visitor->setNavigator($navigator = new GraphNavigator($this->factory, $this->exclusionStrategy));
+        $visitor->setNavigator($navigator = new GraphNavigator(GraphNavigator::DIRECTION_DESERIALIZATION, $this->factory, $this->exclusionStrategy));
         $navigator->accept($visitor->prepare($data), $type, $visitor);
 
         return $visitor->getResult();

+ 36 - 0
Tests/Fixtures/SimpleObjectProxy.php

@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
+ *
+ * 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\SerializerBundle\Tests\Fixtures;
+
+use Doctrine\ORM\Proxy\Proxy;
+
+class SimpleObjectProxy extends SimpleObject implements Proxy
+{
+    public $__isInitialized__ = false;
+
+    private $baz = 'baz';
+
+    public function __load()
+    {
+        if (!$this->__isInitialized__) {
+            $this->camelCase = 'proxy-boo';
+            $this->__isInitialized__ = true;
+        }
+    }
+}

+ 14 - 0
Tests/Serializer/BaseSerializationTest.php

@@ -44,6 +44,7 @@ use JMS\SerializerBundle\Serializer\Handler\ObjectBasedCustomHandler;
 use JMS\SerializerBundle\Serializer\Handler\DateTimeHandler;
 use JMS\SerializerBundle\Serializer\Handler\FormErrorHandler;
 use JMS\SerializerBundle\Serializer\Handler\ConstraintViolationHandler;
+use JMS\SerializerBundle\Serializer\Handler\DoctrineOrmProxyHandler;
 use JMS\SerializerBundle\Tests\Fixtures\Comment;
 use JMS\SerializerBundle\Tests\Fixtures\Author;
 use JMS\SerializerBundle\Tests\Fixtures\BlogPost;
@@ -54,6 +55,7 @@ use Doctrine\Common\Annotations\AnnotationReader;
 use JMS\SerializerBundle\Metadata\Driver\AnnotationDriver;
 use Metadata\MetadataFactory;
 use JMS\SerializerBundle\Tests\Fixtures\SimpleObject;
+use JMS\SerializerBundle\Tests\Fixtures\SimpleObjectProxy;
 use JMS\SerializerBundle\Tests\Fixtures\Price;
 use JMS\SerializerBundle\Serializer\Naming\CamelCaseNamingStrategy;
 use JMS\SerializerBundle\Serializer\Naming\SerializedNameAnnotationStrategy;
@@ -334,6 +336,17 @@ abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($this->getContent('constraint_violation_list'), $this->serialize($violations));
     }
 
+    public function testDoctrineOrmProxy()
+    {
+        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));
+    }
+
     abstract protected function getContent($key);
     abstract protected function getFormat();
 
@@ -390,6 +403,7 @@ abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase
             new DateTimeHandler(),
             new FormErrorHandler($translatorMock),
             new ConstraintViolationHandler(),
+            new DoctrineOrmProxyHandler(),
         );
 
         return $handlers;

+ 1 - 0
Tests/Serializer/JsonSerializationTest.php

@@ -54,6 +54,7 @@ class JsonSerializationTest extends BaseSerializationTest
             $outputs['constraint_violation'] = '{"property_path":"foo","message":"Message of violation"}';
             $outputs['constraint_violation_list'] = '[{"property_path":"foo","message":"Message of violation"},{"property_path":"bar","message":"Message of another violation"}]';
             $outputs['article'] = '{"custom":"serialized"}';
+            $outputs['orm_proxy'] = '{"foo":"foo","moo":"bar","camel_case":"proxy-boo"}';
         }
 
         if (!isset($outputs[$key])) {

+ 6 - 0
Tests/Serializer/xml/orm_proxy.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<result>
+  <foo><![CDATA[foo]]></foo>
+  <moo><![CDATA[bar]]></moo>
+  <camel_case><![CDATA[proxy-boo]]></camel_case>
+</result>

+ 3 - 0
Tests/Serializer/yml/orm_proxy.yml

@@ -0,0 +1,3 @@
+foo: foo
+moo: bar
+camel_case: proxy-boo

+ 1 - 2
composer.json

@@ -2,8 +2,7 @@
     "name": "jms/serializer-bundle",
     "type": "symfony-bundle",
     "description": "Allows you to easily serialize, and deserialize object graphs of any complexity",
-    "keywords": ["serialization"],
-    "homepage": "http://friendsofsymfony.github.com",
+    "keywords": ["serialization", "deserialization", "json", "jaxb", "xml"],
     "license": "Apache",
     "authors": [
         {