Selaa lähdekoodia

added XML, YAML, and PHP metadata drivers

Johannes Schmitt 14 vuotta sitten
vanhempi
commit
29ae0ece64

+ 24 - 0
DependencyInjection/Compiler/SetMetadataDriversPass.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace JMS\SerializerBundle\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+class SetMetadataDriversPass implements CompilerPassInterface
+{
+    public function process(ContainerBuilder $container)
+    {
+        $drivers = array();
+        foreach ($container->findTaggedServiceIds('jms_serializer.metadata_driver') as $id => $attr) {
+            $drivers[] = new Reference($id);
+        }
+
+        $container
+            ->getDefinition('jms_serializer.metadata.chain_driver')
+            ->addArgument($drivers)
+        ;
+    }
+}

+ 12 - 1
DependencyInjection/Configuration.php

@@ -61,13 +61,24 @@ class Configuration implements ConfigurationInterface
                     ->end()
                     ->arrayNode('metadata')
                         ->addDefaultsIfNotSet()
+                        ->fixXmlConfig('directory', 'directories')
                         ->children()
                             ->scalarNode('cache')->defaultValue('file')->end()
                             ->booleanNode('debug')->defaultValue($this->debug)->end()
                             ->arrayNode('file_cache')
                                 ->addDefaultsIfNotSet()
                                 ->children()
-                                    ->scalarNode('dir')->defaultValue('%kernel.cache_dir%/serializer')
+                                    ->scalarNode('dir')->defaultValue('%kernel.cache_dir%/serializer')->end()
+                                ->end()
+                            ->end()
+                            ->booleanNode('enable_annotations')->defaultTrue()->end()
+                            ->booleanNode('auto_detection')->defaultTrue()->end()
+                            ->arrayNode('directories')
+                                ->prototype('array')
+                                    ->children()
+                                        ->scalarNode('path')->isRequired()->end()
+                                        ->scalarNode('namespace_prefix')->defaultValue('')->end()
+                                    ->end()
                                 ->end()
                             ->end()
                         ->end()

+ 23 - 1
DependencyInjection/JMSSerializerExtension.php

@@ -18,8 +18,8 @@
 
 namespace JMS\SerializerBundle\DependencyInjection;
 
+use Symfony\Component\HttpKernel\KernelInterface;
 use Symfony\Component\DependencyInjection\Alias;
-
 use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use JMS\SerializerBundle\Exception\RuntimeException;
 use Symfony\Component\Config\FileLocator;
@@ -82,6 +82,28 @@ class JMSSerializerExtension extends Extension
             ->getDefinition('jms_serializer.metadata_factory')
             ->replaceArgument(2, $config['metadata']['debug'])
         ;
+
+        // directories
+        $directories = array();
+        if ($config['metadata']['auto_detection']) {
+            foreach ($container->getParameter('kernel.bundles') as $name => $class) {
+                $ref = new \ReflectionClass($class);
+
+                $directories[$ref->getNamespaceName()] = dirname($ref->getFileName()).'/Resources/config/serializer';
+            }
+        }
+        foreach ($config['metadata']['directories'] as $directory) {
+            $directories[rtrim($directory['namespace_prefix'], '\\')] = rtrim($directory['path'], '\\/');
+        }
+        $container
+            ->getDefinition('jms_serializer.metadata.file_locator')
+            ->replaceArgument(0, $directories)
+        ;
+
+        // annotation driver
+        if (!$config['metadata']['enable_annotations']) {
+            $container->remove('jms_serializer.metadata.annotation_driver');
+        }
     }
 
     private function mergeConfigs(array $configs, $debug)

+ 2 - 0
JMSSerializerBundle.php

@@ -18,6 +18,7 @@
 
 namespace JMS\SerializerBundle;
 
+use JMS\SerializerBundle\DependencyInjection\Compiler\SetMetadataDriversPass;
 use JMS\SerializerBundle\DependencyInjection\Compiler\SetCustomHandlersPass;
 use JMS\SerializerBundle\DependencyInjection\Compiler\SetVisitorsPass;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -29,5 +30,6 @@ class JMSSerializerBundle extends Bundle
     {
         $builder->addCompilerPass(new SetVisitorsPass());
         $builder->addCompilerPass(new SetCustomHandlersPass());
+        $builder->addCompilerPass(new SetMetadataDriversPass());
     }
 }

+ 0 - 1
Metadata/Driver/AnnotationDriver.php

@@ -19,7 +19,6 @@
 namespace JMS\SerializerBundle\Metadata\Driver;
 
 use JMS\SerializerBundle\Annotation\XmlMap;
-
 use JMS\SerializerBundle\Annotation\XmlRoot;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlList;

+ 28 - 0
Metadata/Driver/PhpDriver.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace JMS\SerializerBundle\Metadata\Driver;
+
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+use Metadata\Driver\AbstractFileDriver;
+
+class PhpDriver extends AbstractFileDriver
+{
+    protected function loadMetadataFromFile(\ReflectionClass $class, $file)
+    {
+        $metadata = require $file;
+
+        if (!$metadata instanceof ClassMetadata) {
+            throw new \RuntimeException(sprintf('The file %s was expected to return an instance of ClassMetadata, but returned %s.', $file, json_encode($metadata)));
+        }
+        if ($metadata->name !== $class->getName()) {
+            throw new \RuntimeException(sprintf('The file %s was expected to return metadata for class %s, but instead returned metadata for class %s.', $class->getName(), $metadata->name));
+        }
+
+        return $metadata;
+    }
+
+    protected function getExtension()
+    {
+        return 'php';
+    }
+}

+ 152 - 0
Metadata/Driver/XmlDriver.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace JMS\SerializerBundle\Metadata\Driver;
+
+use JMS\SerializerBundle\Annotation\ExclusionPolicy;
+use JMS\SerializerBundle\Metadata\PropertyMetadata;
+use Metadata\MethodMetadata;
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+use Metadata\Driver\AbstractFileDriver;
+
+class XmlDriver extends AbstractFileDriver
+{
+    protected function loadMetadataFromFile(\ReflectionClass $class, $path)
+    {
+        $previous = libxml_use_internal_errors(true);
+        $elem = simplexml_load_file($path);
+        libxml_use_internal_errors($previous);
+
+        if (false === $elem) {
+            throw new \RuntimeException('Could not parse XML: '.libxml_get_last_error());
+        }
+
+        $metadata = new ClassMetadata($name = $class->getName());
+        if (!$elems = $elem->xpath("./class[@name = '".$name."']")) {
+            throw new \RuntimeException(sprintf('Could not find class %s inside XML element.', $name));
+        }
+        $elem = reset($elems);
+
+        $metadata->fileResources[] = $path;
+        $metadata->fileResources[] = $class->getFileName();
+        $exclusionPolicy = $elem->attributes()->{'exclusion-policy'} ?: 'NONE';
+        $excludeAll = null !== ($exclude = $elem->attributes()->exclude) ? 'true' === (string) $exclude : false;
+
+        if (null !== $xmlRootName = $elem->attributes()->{'xml-root-name'}) {
+            $metadata->xmlRootName = (string) $xmlRootName;
+        }
+
+        if (!$excludeAll) {
+            foreach ($class->getProperties() as $property) {
+                if ($name !== $property->getDeclaringClass()->getName()) {
+                    continue;
+                }
+
+                $pMetadata = new PropertyMetadata($name, $pName = $property->getName());
+                $isExclude = $isExpose = false;
+
+                $pElems = $elem->xpath("./property[@name = '".$pName."']");
+                if ($pElems) {
+                    $pElem = reset($pElems);
+
+                    if (null !== $exclude = $pElem->attributes()->exclude) {
+                        $isExclude = 'true' === (string) $exclude;
+                    }
+
+                    if (null !== $expose = $pElem->attributes()->expose) {
+                        $isExpose = 'true' === (string) $expose;
+                    }
+
+                    if (null !== $version = $pElem->attributes()->{'since-version'}) {
+                        $pMetadata->sinceVersion = (string) $version;
+                    }
+
+                    if (null !== $version = $pElem->attributes()->{'until-version'}) {
+                        $pMetadata->untilVersion = (string) $version;
+                    }
+
+                    if (null !== $serializedName = $pElem->attributes()->{'serialized-name'}) {
+                        $pMetadata->serializedName = (string) $serializedName;
+                    }
+
+                    if (null !== $type = $pElem->attributes()->type) {
+                        $pMetadata->type = (string) $type;
+                    } else if (isset($pElem->type)) {
+                        $pMetadata->type = (string) $pElem->type;
+                    }
+
+                    if (isset($pElem->{'xml-list'})) {
+                        $pMetadata->xmlCollection = true;
+
+                        $colConfig = $pElem->{'xml-list'};
+                        if (isset($colConfig->attributes()->inline)) {
+                            $pMetadata->xmlCollectionInline = 'true' === (string) $colConfig->attributes()->inline;
+                        }
+
+                        if (isset($colConfig->attributes()->{'entry-name'})) {
+                            $pMetadata->xmlEntryName = (string) $colConfig->attributes()->{'entry-name'};
+                        }
+                    }
+
+                    if (isset($pElem->{'xml-map'})) {
+                        $pMetadata->xmlCollection = true;
+
+                        $colConfig = $pElem->{'xml-map'};
+                        if (isset($colConfig->attributes()->inline)) {
+                            $pMetadata->xmlCollectionInline = 'true' === $colConfig->attributes()->inline;
+                        }
+
+                        if (isset($colConfig->attributes()->{'entry-name'})) {
+                            $pMetadata->xmlEntryName = (string) $colConfig->attributes()->{'entry-name'};
+                        }
+
+                        if (isset($colConfig->attributes()->{'key-attribute-name'})) {
+                            $pMetadata->xmlKeyAttribute = $colConfig->attributes()->{'key-attribute-name'};
+                        }
+                    }
+
+                    if (isset($pElem->attributes()->{'xml-attribute'})) {
+                        $pMetadata->xmlAttribute = 'true' === (string) $pElem->attributes()->{'xml-attribute'};
+                    }
+
+                    if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
+                        || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)) {
+                        $metadata->addPropertyMetadata($pMetadata);
+                    }
+                }
+            }
+        }
+
+        foreach ($elem->xpath('./callback-method') as $method) {
+            if (!isset($method->attributes()->type)) {
+                throw new \RuntimeException('The type attribute must be set for all callback-method elements.');
+            }
+            if (!isset($method->attributes()->name)) {
+                throw new \RuntimeException('The name attribute must be set for all callback-method elements.');
+            }
+
+            switch ((string) $method->attributes()->type) {
+                case 'pre-serialize':
+                    $metadata->addPreSerializeMethod(new MethodMetadata($name, (string) $method->attributes()->name));
+                    break;
+
+                case 'post-serialize':
+                    $metadata->addPostSerializeMethod(new MethodMetadata($name, (string) $method->attributes()->name));
+                    break;
+
+                case 'post-deserialize':
+                    $metadata->addPostDeserializeMethod(new MethodMetadata($name, (string) $method->attributes()->name));
+                    break;
+
+                default:
+                    throw new \RuntimeException(sprintf('The type "%s" is not supported.', $method->attributes()->name));
+            }
+        }
+
+        return $metadata;
+    }
+
+    protected function getExtension()
+    {
+        return 'xml';
+    }
+}

+ 151 - 0
Metadata/Driver/YamlDriver.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace JMS\SerializerBundle\Metadata\Driver;
+
+use JMS\SerializerBundle\Annotation\ExclusionPolicy;
+use Metadata\MethodMetadata;
+use JMS\SerializerBundle\Metadata\PropertyMetadata;
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+use Symfony\Component\Yaml\Yaml;
+use Metadata\Driver\AbstractFileDriver;
+
+class YamlDriver extends AbstractFileDriver
+{
+    protected function loadMetadataFromFile(\ReflectionClass $class, $file)
+    {
+        $config = Yaml::parse(file_get_contents($file));
+
+        if (!isset($config[$name = $class->getName()])) {
+            throw new \RuntimeException(sprintf('Expected metadata for class %s to be defined in %s.', $class->getName(), $file));
+        }
+
+        $config = $config[$name];
+        $metadata = new ClassMetadata($name);
+        $metadata->fileResources[] = $file;
+        $metadata->fileResources[] = $class->getFileName();
+        $exclusionPolicy = isset($config['exclusion_policy']) ? $config['exclusion_policy'] : 'NONE';
+        $excludeAll = isset($config['exclude']) ? (Boolean) $config['exclude'] : false;
+
+        if (isset($config['xml_root_name'])) {
+            $metadata->xmlRootName = (string) $config['xml_root_name'];
+        }
+
+        if (!$excludeAll) {
+            foreach ($class->getProperties() as $property) {
+                if ($name !== $property->getDeclaringClass()->getName()) {
+                    continue;
+                }
+
+                $pMetadata = new PropertyMetadata($name, $pName = $property->getName());
+                $isExclude = $isExpose = false;
+                if (isset($config['properties'][$pName])) {
+                    $pConfig = $config['properties'][$pName];
+
+                    if (isset($pConfig['exclude'])) {
+                        $isExclude = (Boolean) $pConfig['exclude'];
+                    }
+
+                    if (isset($pConfig['expose'])) {
+                        $isExpose = (Boolean) $pConfig['expose'];
+                    }
+
+                    if (isset($pConfig['since_version'])) {
+                        $pMetadata->sinceVersion = (string) $pConfig['since_version'];
+                    }
+
+                    if (isset($pConfig['until_version'])) {
+                        $pMetadata->untilVersion = (string) $pConfig['until_version'];
+                    }
+
+                    if (isset($pConfig['serialized_name'])) {
+                        $pMetadata->serializedName = (string) $pConfig['serialized_name'];
+                    }
+
+                    if (isset($pConfig['type'])) {
+                        $pMetadata->type = (string) $pConfig['type'];
+                    }
+
+                    if (isset($pConfig['xml_list'])) {
+                        $pMetadata->xmlCollection = true;
+
+                        $colConfig = $pConfig['xml_list'];
+                        if (isset($colConfig['inline'])) {
+                            $pMetadata->xmlCollectionInline = (Boolean) $colConfig['inline'];
+                        }
+
+                        if (isset($colConfig['entry_name'])) {
+                            $pMetadata->xmlEntryName = (string) $colConfig['entry_name'];
+                        }
+                    }
+
+                    if (isset($pConfig['xml_map'])) {
+                        $pMetadata->xmlCollection = true;
+
+                        $colConfig = $pConfig['xml_map'];
+                        if (isset($colConfig['inline'])) {
+                            $pMetadata->xmlCollectionInline = (Boolean) $colConfig['inline'];
+                        }
+
+                        if (isset($colConfig['entry_name'])) {
+                            $pMetadata->xmlEntryName = (string) $colConfig['entry_name'];
+                        }
+
+                        if (isset($colConfig['key_attribute_name'])) {
+                            $pMetadata->xmlKeyAttribute = $colConfig['key_attribute_name'];
+                        }
+                    }
+
+                    if (isset($pConfig['xml_attribute'])) {
+                        $pMetadata->xmlAttribute = (Boolean) $pConfig['xml_attribute'];
+                    }
+
+                    if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
+                    || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)) {
+                        $metadata->addPropertyMetadata($pMetadata);
+                    }
+                }
+            }
+        }
+
+        if (isset($config['callback_methods'])) {
+            $cConfig = $config['callback_methods'];
+
+            if (isset($cConfig['pre_serialize'])) {
+                $metadata->preSerializeMethods = $this->getCallbackMetadata($class, $cConfig['pre_serialize']);
+            }
+            if (isset($cConfig['post_serialize'])) {
+                $metadata->postSerializeMethods = $this->getCallbackMetadata($class, $cConfig['post_serialize']);
+            }
+            if (isset($cConfig['post_deserialize'])) {
+                $metadata->postDeserializeMethods = $this->getCallbackMetadata($class, $cConfig['post_deserialize']);
+            }
+        }
+
+        return $metadata;
+    }
+
+    protected function getExtension()
+    {
+        return 'yml';
+    }
+
+    private function getCallbackMetadata(\ReflectionClass $class, $config)
+    {
+        if (is_string($config)) {
+            $config = array($config);
+        } else if (!is_array($config)) {
+            throw new \RuntimeException(sprintf('callback methods expects a string, or an array of strings that represent method names, but got %s.', json_encode($cConfig['pre_serialize'])));
+        }
+
+        $methods = array();
+        foreach ($config as $name) {
+            if (!$class->hasMethod($name)) {
+                throw new \RuntimeException(sprintf('The method %s does not exist in class %s.', $mName, $name));
+            }
+
+            $methods[] = new MethodMetadata($class->getName(), $name);
+        }
+
+        return $methods;
+    }
+}

+ 1 - 1
Metadata/PropertyMetadata.php

@@ -27,7 +27,7 @@ class PropertyMetadata extends BasePropertyMetadata
     public $serializedName;
     public $type;
     public $xmlCollection = false;
-    public $xmlCollectionInline;
+    public $xmlCollectionInline = false;
     public $xmlEntryName;
     public $xmlKeyAttribute;
     public $xmlAttribute = false;

+ 31 - 4
Resources/config/services.xml

@@ -5,7 +5,13 @@
     xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
     <parameters>
-        <parameter key="jms_serializer.metadata.driver.annotation_driver.class">JMS\SerializerBundle\Metadata\Driver\AnnotationDriver</parameter>
+        <parameter key="jms_serializer.metadata.file_locator.class">Metadata\Driver\FileLocator</parameter>
+        <parameter key="jms_serializer.metadata.annotation_driver.class">JMS\SerializerBundle\Metadata\Driver\AnnotationDriver</parameter>
+        <parameter key="jms_serializer.metadata.chain_driver.class">Metadata\Driver\DriverChain</parameter>
+        <parameter key="jms_serializer.metadata.yaml_driver.class">JMS\SerializerBundle\Metadata\Driver\YamlDriver</parameter>
+        <parameter key="jms_serializer.metadata.xml_driver.class">JMS\SerializerBundle\Metadata\Driver\XmlDriver</parameter>
+        <parameter key="jms_serializer.metadata.php_driver.class">JMS\SerializerBundle\Metadata\Driver\PhpDriver</parameter>
+        <parameter key="jms_serializer.metadata.lazy_loading_driver.class">Metadata\Driver\LazyLoadingDriver</parameter>
         
         <parameter key="jms_serializer.metadata.metadata_factory.class">Metadata\MetadataFactory</parameter>
         <parameter key="jms_serializer.metadata.cache.file_cache.class">Metadata\Cache\FileCache</parameter>
@@ -30,10 +36,31 @@
 
     <services>
         <!-- Metadata Drivers -->
-        <service id="jms_serializer.metadata.driver.annotation_driver" class="%jms_serializer.metadata.driver.annotation_driver.class%" public="false">
+        <service id="jms_serializer.metadata.file_locator" class="%jms_serializer.metadata.file_locator.class%" public="false">
+            <argument type="collection" /><!-- Namespace Prefixes mapping to Directories -->
+        </service>
+        <service id="jms_serializer.metadata.yaml_driver" class="%jms_serializer.metadata.yaml_driver.class%" public="false">
+            <argument type="service" id="jms_serializer.metadata.file_locator" />
+            <tag name="jms_serializer.metadata_driver" />
+        </service>
+        <service id="jms_serializer.metadata.xml_driver" class="%jms_serializer.metadata.xml_driver.class%" public="false">
+            <argument type="service" id="jms_serializer.metadata.file_locator" />
+            <tag name="jms_serializer.metadata_driver" />
+        </service>
+        <service id="jms_serializer.metadata.php_driver" class="%jms_serializer.metadata.php_driver.class%" public="false">
+            <argument type="service" id="jms_serializer.metadata.file_locator" />
+            <tag name="jms_serializer.metadata_driver" />
+        </service>
+        <service id="jms_serializer.metadata.annotation_driver" class="%jms_serializer.metadata.annotation_driver.class%" public="false">
             <argument type="service" id="annotation_reader" />
+            <tag name="jms_serializer.metadata_driver" />
+        </service>
+        <service id="jms_serializer.metadata.chain_driver" class="%jms_serializer.metadata.chain_driver.class%" public="false" />
+        <service id="jms_serializer.metadata.lazy_loading_driver" class="%jms_serializer.metadata.lazy_loading_driver.class%">
+            <argument type="service" id="service_container" />
+            <argument>jms_serializer.metadata_driver</argument>
         </service>
-        <service id="jms_serializer.metadata_driver" alias="jms_serializer.metadata.driver.annotation_driver" public="false" />
+        <service id="jms_serializer.metadata_driver" alias="jms_serializer.metadata.chain_driver" />
 
         <!-- Metadata Factory -->
         <service id="jms_serializer.metadata.cache.file_cache" class="%jms_serializer.metadata.cache.file_cache.class%" public="false">
@@ -41,7 +68,7 @@
         </service>
         <service id="jms_serializer.metadata.cache" alias="jms_serializer.metadata.cache.file_cache" public="false" />
         <service id="jms_serializer.metadata_factory" class="%jms_serializer.metadata.metadata_factory.class%" public="false">
-            <argument type="service" id="jms_serializer.metadata_driver" />
+            <argument type="service" id="jms_serializer.metadata.lazy_loading_driver" />
             <argument>Metadata\ClassHierarchyMetadata</argument>
             <argument />
             <call method="setCache">

+ 89 - 2
Resources/doc/index.rst

@@ -11,6 +11,7 @@ include:
 - supports versioning out of the box
 - easily customizable as most logic is implemented using clearly defined
   interfaces
+- can be configured via annotations, YAML, XML, or PHP
 
 This bundle works best when you have full control over the objects that you want
 to serialize/unserialize as you can leverage the full power of annotations then.
@@ -64,7 +65,33 @@ suit your needs::
         property_naming:
             separator:  _
             lower_case: true
-
+            
+        metadata:
+            cache: file
+            debug: %kernel.debug%
+            file_cache:
+                dir: %kernel.cache_dir%/serializer
+            
+            enable_annotations: true
+            
+            # Using auto-detection, the mapping files for each bundle will be 
+            # expected in the Resources/config/serializer directory.
+            # 
+            # Example:
+            # class: My\FooBundle\Entity\User
+            # expected path: @MyFooBundle/Resources/config/serializer/Entity.User.(yml|xml|php)
+            auto_detection: true
+            
+            # if you don't want to use auto-detection, you can also define the 
+            # namespace prefix and the corresponding directory explicitly
+            directories:
+                any-name:
+                    namespace_prefix: My\FooBundle
+                    path: @MyFooBundle/Resources/config/serializer
+                another-name:
+                    namespace_prefix: My\BarBundle
+                    path: @MyBarBundle/Resources/config/serializer
+            
 Usage
 -----
 
@@ -387,4 +414,64 @@ Resulting XML::
 @XmlMap
 ~~~~~~~
 Similar to @XmlList, but the keys of the array are meaningful.    
-    
+
+XML Reference
+-------------
+::
+
+    <!-- MyBundle\Resources\config\serializer\ClassName.xml -->
+    <?xml version="1.0" encoding="UTF-8">
+    <serializer>
+        <class name="Fully\Qualified\ClassName" exclusion-policy="ALL" xml-root-name="foo-bar" exclude="true">
+            <property name="some-property" 
+                      exclude="true" 
+                      expose="true" 
+                      type="string" 
+                      serialized-name="foo" 
+                      since-version="1.0" 
+                      until-version="1.1"
+                      xml-attribute="true"
+            >
+                <!-- You can also specify the type as element which is necessary if
+                     your type contains "<" or ">" characters. -->
+                <type><![CDATA[]]></type>
+                <xml-list inline="true" entry-name="foobar" />
+                <xml-map inline="true" key-attribute-name="foo" entry-name="bar" />
+            </property>
+            <callback-method name="foo" event="pre-serialize" />
+            <callback-method name="bar" event="post-serialize" />
+            <callback-method name="baz" event="post-deserialize" />
+        </class>
+    </serializer>
+
+YAML Reference
+--------------
+::
+
+    # MyBundle\Resources\config\serializer\ClassName.xml
+    Fully\Qualified\ClassName:
+        exclusion_policy: ALL
+        xml_root_name: foobar
+        exclude: true
+        properties:
+            some-property:
+                exclude: true
+                expose: true
+                type: string
+                serialized_name: foo
+                since_version: 1.0
+                until_version: 1.1
+                xml_attribute: true
+                xml_list:
+                    inline: true
+                    entry_name: foo
+                xml_map:
+                    inline: true
+                    key_attribute_name: foo
+                    entry_name: bar
+        callback_methods:
+            pre_serialize: [foo, bar]
+            post_serialize: [foo, bar]
+            post_deserialize: [foo, bar]
+
+

+ 1 - 0
Tests/DependencyInjection/JMSSerializerExtensionTest.php

@@ -56,6 +56,7 @@ class JMSSerializerExtensionTest extends \PHPUnit_Framework_TestCase
         $container = new ContainerBuilder();
         $container->setParameter('kernel.debug', true);
         $container->setParameter('kernel.cache_dir', sys_get_temp_dir());
+        $container->setParameter('kernel.bundles', array());
         $container->set('annotation_reader', new AnnotationReader());
         $container->set('service_container', $container);
         $extension->load(array(array()), $container);

+ 14 - 0
Tests/Metadata/Driver/AnnotationDriverTest.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Metadata\Driver;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use JMS\SerializerBundle\Metadata\Driver\AnnotationDriver;
+
+class AnnotationDriverTest extends BaseDriverTest
+{
+    protected function getDriver()
+    {
+        return new AnnotationDriver(new AnnotationReader());
+    }
+}

+ 45 - 0
Tests/Metadata/Driver/BaseDriverTest.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Metadata\Driver;
+
+use JMS\SerializerBundle\Metadata\PropertyMetadata;
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+
+abstract class BaseDriverTest extends \PHPUnit_Framework_TestCase
+{
+    public function testLoadBlogPostMetadata()
+    {
+        $m = $this->getDriver()->loadMetadataForClass(new \ReflectionClass('JMS\SerializerBundle\Tests\Fixtures\BlogPost'));
+
+        $this->assertNotNull($m);
+        $this->assertEquals('blog-post', $m->xmlRootName);
+
+        $p = new PropertyMetadata($m->name, 'title');
+        $p->type = 'string';
+        $this->assertEquals($p, $m->propertyMetadata['title']);
+
+        $p = new PropertyMetadata($m->name, 'createdAt');
+        $p->type = 'DateTime';
+        $p->xmlAttribute = true;
+        $this->assertEquals($p, $m->propertyMetadata['createdAt']);
+
+        $p = new PropertyMetadata($m->name, 'published');
+        $p->type = 'boolean';
+        $p->serializedName = 'is_published';
+        $p->xmlAttribute = true;
+        $this->assertEquals($p, $m->propertyMetadata['published']);
+
+        $p = new PropertyMetadata($m->name, 'comments');
+        $p->type = 'ArrayCollection<JMS\SerializerBundle\Tests\Fixtures\Comment>';
+        $p->xmlCollection = true;
+        $p->xmlCollectionInline = true;
+        $p->xmlEntryName = 'comment';
+        $this->assertEquals($p, $m->propertyMetadata['comments']);
+
+        $p = new PropertyMetadata($m->name, 'author');
+        $p->type = 'JMS\SerializerBundle\Tests\Fixtures\Author';
+        $this->assertEquals($p, $m->propertyMetadata['author']);
+    }
+
+    abstract protected function getDriver();
+}

+ 16 - 0
Tests/Metadata/Driver/PhpDriverTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Metadata\Driver;
+
+use Metadata\Driver\FileLocator;
+use JMS\SerializerBundle\Metadata\Driver\PhpDriver;
+
+class PhpDriverTest extends BaseDriverTest
+{
+    protected function getDriver()
+    {
+        return new PhpDriver(new FileLocator(array(
+            'JMS\SerializerBundle\Tests\Fixtures' => __DIR__.'/php',
+        )));
+    }
+}

+ 16 - 0
Tests/Metadata/Driver/XmlDriverTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Metadata\Driver;
+
+use Metadata\Driver\FileLocator;
+use JMS\SerializerBundle\Metadata\Driver\XmlDriver;
+
+class XmlDriverTest extends BaseDriverTest
+{
+    protected function getDriver()
+    {
+        return new XmlDriver(new FileLocator(array(
+            'JMS\SerializerBundle\Tests\Fixtures' => __DIR__.'/xml',
+        )));
+    }
+}

+ 16 - 0
Tests/Metadata/Driver/YamlDriverTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Metadata\Driver;
+
+use Metadata\Driver\FileLocator;
+use JMS\SerializerBundle\Metadata\Driver\YamlDriver;
+
+class YamlDriverTest extends BaseDriverTest
+{
+    protected function getDriver()
+    {
+        return new YamlDriver(new FileLocator(array(
+            'JMS\SerializerBundle\Tests\Fixtures' => __DIR__.'/yml',
+        )));
+    }
+}

+ 35 - 0
Tests/Metadata/Driver/php/BlogPost.php

@@ -0,0 +1,35 @@
+<?php
+
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+use JMS\SerializerBundle\Metadata\PropertyMetadata;
+
+$metadata = new ClassMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost');
+$metadata->xmlRootName = 'blog-post';
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost', 'title');
+$pMetadata->type = 'string';
+$metadata->addPropertyMetadata($pMetadata);
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost', 'createdAt');
+$pMetadata->type = 'DateTime';
+$pMetadata->xmlAttribute = true;
+$metadata->addPropertyMetadata($pMetadata);
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost', 'published');
+$pMetadata->type = 'boolean';
+$pMetadata->serializedName = 'is_published';
+$pMetadata->xmlAttribute = true;
+$metadata->addPropertyMetadata($pMetadata);
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost', 'comments');
+$pMetadata->type = 'ArrayCollection<JMS\SerializerBundle\Tests\Fixtures\Comment>';
+$pMetadata->xmlCollection = true;
+$pMetadata->xmlCollectionInline = true;
+$pMetadata->xmlEntryName = 'comment';
+$metadata->addPropertyMetadata($pMetadata);
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\BlogPost', 'author');
+$pMetadata->type = 'JMS\SerializerBundle\Tests\Fixtures\Author';
+$metadata->addPropertyMetadata($pMetadata);
+
+return $metadata;

+ 13 - 0
Tests/Metadata/Driver/xml/BlogPost.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<serializer>
+    <class name="JMS\SerializerBundle\Tests\Fixtures\BlogPost" xml-root-name="blog-post">
+        <property name="title" type="string" />
+        <property name="createdAt" xml-attribute="true" type="DateTime" />
+        <property name="published" type="boolean" serialized-name="is_published" xml-attribute="true" />
+        <property name="comments">
+            <type><![CDATA[ArrayCollection<JMS\SerializerBundle\Tests\Fixtures\Comment>]]></type>
+            <xml-list inline="true" entry-name="comment" />
+        </property>
+        <property name="author" type="JMS\SerializerBundle\Tests\Fixtures\Author" />
+    </class>
+</serializer>

+ 19 - 0
Tests/Metadata/Driver/yml/BlogPost.yml

@@ -0,0 +1,19 @@
+JMS\SerializerBundle\Tests\Fixtures\BlogPost:
+    xml_root_name: blog-post
+    properties:
+        title:
+            type: string
+        createdAt:
+            type: DateTime
+            xml_attribute: true
+        published:
+            type: boolean
+            serialized_name: is_published
+            xml_attribute: true
+        comments:
+            type: ArrayCollection<JMS\SerializerBundle\Tests\Fixtures\Comment>
+            xml_list:
+                inline: true
+                entry_name: comment
+        author:
+            type: JMS\SerializerBundle\Tests\Fixtures\Author