Browse Source

added XML, YAML, and PHP metadata drivers

Johannes Schmitt 14 years ago
parent
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()
                     ->end()
                     ->arrayNode('metadata')
                     ->arrayNode('metadata')
                         ->addDefaultsIfNotSet()
                         ->addDefaultsIfNotSet()
+                        ->fixXmlConfig('directory', 'directories')
                         ->children()
                         ->children()
                             ->scalarNode('cache')->defaultValue('file')->end()
                             ->scalarNode('cache')->defaultValue('file')->end()
                             ->booleanNode('debug')->defaultValue($this->debug)->end()
                             ->booleanNode('debug')->defaultValue($this->debug)->end()
                             ->arrayNode('file_cache')
                             ->arrayNode('file_cache')
                                 ->addDefaultsIfNotSet()
                                 ->addDefaultsIfNotSet()
                                 ->children()
                                 ->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()
                             ->end()
                         ->end()
                         ->end()

+ 23 - 1
DependencyInjection/JMSSerializerExtension.php

@@ -18,8 +18,8 @@
 
 
 namespace JMS\SerializerBundle\DependencyInjection;
 namespace JMS\SerializerBundle\DependencyInjection;
 
 
+use Symfony\Component\HttpKernel\KernelInterface;
 use Symfony\Component\DependencyInjection\Alias;
 use Symfony\Component\DependencyInjection\Alias;
-
 use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use JMS\SerializerBundle\Exception\RuntimeException;
 use JMS\SerializerBundle\Exception\RuntimeException;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Component\Config\FileLocator;
@@ -82,6 +82,28 @@ class JMSSerializerExtension extends Extension
             ->getDefinition('jms_serializer.metadata_factory')
             ->getDefinition('jms_serializer.metadata_factory')
             ->replaceArgument(2, $config['metadata']['debug'])
             ->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)
     private function mergeConfigs(array $configs, $debug)

+ 2 - 0
JMSSerializerBundle.php

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

+ 0 - 1
Metadata/Driver/AnnotationDriver.php

@@ -19,7 +19,6 @@
 namespace JMS\SerializerBundle\Metadata\Driver;
 namespace JMS\SerializerBundle\Metadata\Driver;
 
 
 use JMS\SerializerBundle\Annotation\XmlMap;
 use JMS\SerializerBundle\Annotation\XmlMap;
-
 use JMS\SerializerBundle\Annotation\XmlRoot;
 use JMS\SerializerBundle\Annotation\XmlRoot;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlList;
 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 $serializedName;
     public $type;
     public $type;
     public $xmlCollection = false;
     public $xmlCollection = false;
-    public $xmlCollectionInline;
+    public $xmlCollectionInline = false;
     public $xmlEntryName;
     public $xmlEntryName;
     public $xmlKeyAttribute;
     public $xmlKeyAttribute;
     public $xmlAttribute = false;
     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">
     xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
 
     <parameters>
     <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.metadata_factory.class">Metadata\MetadataFactory</parameter>
         <parameter key="jms_serializer.metadata.cache.file_cache.class">Metadata\Cache\FileCache</parameter>
         <parameter key="jms_serializer.metadata.cache.file_cache.class">Metadata\Cache\FileCache</parameter>
@@ -30,10 +36,31 @@
 
 
     <services>
     <services>
         <!-- Metadata Drivers -->
         <!-- 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" />
             <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>
-        <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 -->
         <!-- Metadata Factory -->
         <service id="jms_serializer.metadata.cache.file_cache" class="%jms_serializer.metadata.cache.file_cache.class%" public="false">
         <service id="jms_serializer.metadata.cache.file_cache" class="%jms_serializer.metadata.cache.file_cache.class%" public="false">
@@ -41,7 +68,7 @@
         </service>
         </service>
         <service id="jms_serializer.metadata.cache" alias="jms_serializer.metadata.cache.file_cache" public="false" />
         <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">
         <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>Metadata\ClassHierarchyMetadata</argument>
             <argument />
             <argument />
             <call method="setCache">
             <call method="setCache">

+ 89 - 2
Resources/doc/index.rst

@@ -11,6 +11,7 @@ include:
 - supports versioning out of the box
 - supports versioning out of the box
 - easily customizable as most logic is implemented using clearly defined
 - easily customizable as most logic is implemented using clearly defined
   interfaces
   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
 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.
 to serialize/unserialize as you can leverage the full power of annotations then.
@@ -64,7 +65,33 @@ suit your needs::
         property_naming:
         property_naming:
             separator:  _
             separator:  _
             lower_case: true
             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
 Usage
 -----
 -----
 
 
@@ -387,4 +414,64 @@ Resulting XML::
 @XmlMap
 @XmlMap
 ~~~~~~~
 ~~~~~~~
 Similar to @XmlList, but the keys of the array are meaningful.    
 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 = new ContainerBuilder();
         $container->setParameter('kernel.debug', true);
         $container->setParameter('kernel.debug', true);
         $container->setParameter('kernel.cache_dir', sys_get_temp_dir());
         $container->setParameter('kernel.cache_dir', sys_get_temp_dir());
+        $container->setParameter('kernel.bundles', array());
         $container->set('annotation_reader', new AnnotationReader());
         $container->set('annotation_reader', new AnnotationReader());
         $container->set('service_container', $container);
         $container->set('service_container', $container);
         $extension->load(array(array()), $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