From 6bdaf0bceba3605c0bc4c83d852f1a34ece30c25 Mon Sep 17 00:00:00 2001 From: Titouan Galopin Date: Fri, 29 Jul 2016 18:51:44 +0200 Subject: [PATCH] [FrameworkBundle] Introduce a cache warmer for Validator based on PhpArrayAdapter --- .../CacheWarmer/ValidatorCacheWarmer.php | 112 ++++++++++++++++++ .../DependencyInjection/Configuration.php | 2 +- .../FrameworkExtension.php | 6 +- .../Resources/config/validator.xml | 16 ++- .../CacheWarmer/ValidatorCacheWarmerTest.php | 78 ++++++++++++ .../DependencyInjection/ConfigurationTest.php | 1 - .../DependencyInjection/Fixtures/php/full.php | 1 - .../DependencyInjection/Fixtures/xml/full.xml | 2 +- .../DependencyInjection/Fixtures/yml/full.yml | 1 - .../FrameworkExtensionTest.php | 2 +- .../Tests/Fixtures/Validation/Author.php | 8 ++ .../Tests/Fixtures/Validation/Person.php | 8 ++ .../Fixtures/Validation/Resources/author.yml | 4 + .../Fixtures/Validation/Resources/person.xml | 18 +++ .../Bundle/FrameworkBundle/composer.json | 2 +- .../Mapping/Loader/XmlFileLoader.php | 45 +++++-- .../Mapping/Loader/YamlFileLoader.php | 63 ++++++---- .../Component/Validator/ValidatorBuilder.php | 50 ++++---- 18 files changed, 349 insertions(+), 70 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Author.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Person.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Resources/author.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Resources/person.xml diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php new file mode 100644 index 0000000000000..2e1c86e508998 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\Validator\Mapping\Cache\Psr6Cache; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; +use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; +use Symfony\Component\Validator\ValidatorBuilderInterface; + +/** + * Warms up XML and YAML validator metadata. + * + * @author Titouan Galopin + */ +class ValidatorCacheWarmer implements CacheWarmerInterface +{ + private $validatorBuilder; + private $phpArrayFile; + private $fallbackPool; + + /** + * @param ValidatorBuilderInterface $validatorBuilder + * @param string $phpArrayFile The PHP file where metadata are cached. + * @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached. + */ + public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool) + { + $this->validatorBuilder = $validatorBuilder; + $this->phpArrayFile = $phpArrayFile; + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); + } + $this->fallbackPool = $fallbackPool; + } + + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + if (!method_exists($this->validatorBuilder, 'getLoaders')) { + return; + } + + $adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool); + $arrayPool = new ArrayAdapter(0, false); + + $loaders = $this->validatorBuilder->getLoaders(); + $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayPool)); + + foreach ($this->extractSupportedLoaders($loaders) as $loader) { + foreach ($loader->getMappedClasses() as $mappedClass) { + $metadataFactory->getMetadataFor($mappedClass); + } + } + + $values = $arrayPool->getValues(); + $adapter->warmUp($values); + + foreach ($values as $k => $v) { + $item = $this->fallbackPool->getItem($k); + $this->fallbackPool->saveDeferred($item->set($v)); + } + $this->fallbackPool->commit(); + } + + /** + * {@inheritdoc} + */ + public function isOptional() + { + return true; + } + + /** + * @param LoaderInterface[] $loaders + * + * @return XmlFileLoader[]|YamlFileLoader[] + */ + private function extractSupportedLoaders(array $loaders) + { + $supportedLoaders = array(); + + foreach ($loaders as $loader) { + if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) { + $supportedLoaders[] = $loader; + } elseif ($loader instanceof LoaderChain) { + $supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getDelegatedLoaders())); + } + } + + return $supportedLoaders; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c73c9e26e83cf..a3c9e6e936a21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -567,7 +567,7 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ->info('validation configuration') ->canBeEnabled() ->children() - ->scalarNode('cache')->defaultValue('validator.mapping.cache.symfony')->end() + ->scalarNode('cache')->end() ->booleanNode('enable_annotations')->defaultFalse()->end() ->arrayNode('static_method') ->defaultValue(array('loadValidatorMetadata')) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 806a06197cafc..cf4961ca5461a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -858,13 +858,17 @@ private function registerValidationConfiguration(array $config, ContainerBuilder } } - if (!$container->getParameter('kernel.debug')) { + if (isset($config['cache']) && $config['cache']) { + @trigger_error('The "framework.validation.cache" option is deprecated since Symfony 3.2 and will be removed in 4.0. Configure the "cache.validator" service under "framework.cache.pools" instead.', E_USER_DEPRECATED); + $container->setParameter( 'validator.mapping.cache.prefix', 'validator_'.$this->getKernelRootHash($container) ); $validatorBuilder->addMethodCall('setMetadataCache', array(new Reference($config['cache']))); + } elseif (!$container->getParameter('kernel.debug')) { + $validatorBuilder->addMethodCall('setMetadataCache', array(new Reference('validator.mapping.cache.symfony'))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index ce2221cd6166e..6da59e54e212e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -6,6 +6,7 @@ + %kernel.cache_dir%/validation.php @@ -28,8 +29,21 @@ - + + + %validator.mapping.cache.file% + + + + + + + + %validator.mapping.cache.file% + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php new file mode 100644 index 0000000000000..0e3fe47ce5ff8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Validator\ValidatorBuilder; + +class ValidatorCacheWarmerTest extends TestCase +{ + public function testWarmUp() + { + $validatorBuilder = new ValidatorBuilder(); + $validatorBuilder->addXmlMapping(__DIR__.'/../Fixtures/Validation/Resources/person.xml'); + $validatorBuilder->addYamlMapping(__DIR__.'/../Fixtures/Validation/Resources/author.yml'); + $validatorBuilder->addMethodMapping('loadValidatorMetadata'); + $validatorBuilder->enableAnnotationMapping(); + + $file = sys_get_temp_dir().'/cache-validator.php'; + @unlink($file); + + $fallbackPool = new ArrayAdapter(); + + $warmer = new ValidatorCacheWarmer($validatorBuilder, $file, $fallbackPool); + $warmer->warmUp(dirname($file)); + + $this->assertFileExists($file); + + $values = require $file; + + $this->assertInternalType('array', $values); + $this->assertCount(2, $values); + $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values); + $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values); + + $values = $fallbackPool->getValues(); + + $this->assertInternalType('array', $values); + $this->assertCount(2, $values); + $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values); + $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values); + } + + public function testWarmUpWithoutLoader() + { + $validatorBuilder = new ValidatorBuilder(); + + $file = sys_get_temp_dir().'/cache-validator-without-loaders.php'; + @unlink($file); + + $fallbackPool = new ArrayAdapter(); + + $warmer = new ValidatorCacheWarmer($validatorBuilder, $file, $fallbackPool); + $warmer->warmUp(dirname($file)); + + $this->assertFileExists($file); + + $values = require $file; + + $this->assertInternalType('array', $values); + $this->assertCount(0, $values); + + $values = $fallbackPool->getValues(); + + $this->assertInternalType('array', $values); + $this->assertCount(0, $values); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index bcea36768284b..2030aef0346e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -211,7 +211,6 @@ protected static function getBundleDefaultConfig() 'static_method' => array('loadValidatorMetadata'), 'translation_domain' => 'validators', 'strict_email' => false, - 'cache' => 'validator.mapping.cache.symfony', ), 'annotations' => array( 'cache' => 'php_array', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 4d2bb6e0ca3f1..9173dd5615bda 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -56,7 +56,6 @@ ), 'validation' => array( 'enabled' => true, - 'cache' => 'validator.mapping.cache.doctrine.apc', ), 'annotations' => array( 'cache' => 'file', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 84361e03a422b..7c6b11ac44aa6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -38,7 +38,7 @@ %kernel.root_dir%/Fixtures/translations - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 894cae4f72575..237654880c984 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -44,7 +44,6 @@ framework: paths: ['%kernel.root_dir%/Fixtures/translations'] validation: enabled: true - cache: validator.mapping.cache.doctrine.apc annotations: cache: file debug: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index cbf60967e0f1d..2f647a3793030 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -336,7 +336,7 @@ public function testValidation() $this->assertSame('addMethodMapping', $calls[4][0]); $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); $this->assertSame('setMetadataCache', $calls[5][0]); - $this->assertEquals(array(new Reference('validator.mapping.cache.doctrine.apc')), $calls[5][1]); + $this->assertEquals(array(new Reference('validator.mapping.cache.symfony')), $calls[5][1]); } public function testValidationService() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Author.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Author.php new file mode 100644 index 0000000000000..2a6b48ab4e1ed --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Author.php @@ -0,0 +1,8 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index fe0ebf69db708..6305fd56e8ea9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -48,7 +48,7 @@ "symfony/expression-language": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", "symfony/serializer": "~2.8|^3.0", - "symfony/validator": "~3.1", + "symfony/validator": "~3.2", "symfony/yaml": "~3.2", "symfony/property-info": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0", diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php index 77a556b5b8eeb..f1ee784462732 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php @@ -35,19 +35,7 @@ class XmlFileLoader extends FileLoader public function loadClassMetadata(ClassMetadata $metadata) { if (null === $this->classes) { - // This method may throw an exception. Do not modify the class' - // state before it completes - $xml = $this->parseFile($this->file); - - $this->classes = array(); - - foreach ($xml->namespace as $namespace) { - $this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace)); - } - - foreach ($xml->class as $class) { - $this->classes[(string) $class['name']] = $class; - } + $this->loadClassesFromXml(); } if (isset($this->classes[$metadata->getClassName()])) { @@ -61,6 +49,20 @@ public function loadClassMetadata(ClassMetadata $metadata) return false; } + /** + * Return the names of the classes mapped in this file. + * + * @return string[] The classes names + */ + public function getMappedClasses() + { + if (null === $this->classes) { + $this->loadClassesFromXml(); + } + + return array_keys($this->classes); + } + /** * Parses a collection of "constraint" XML nodes. * @@ -182,6 +184,23 @@ protected function parseFile($path) return simplexml_import_dom($dom); } + private function loadClassesFromXml() + { + // This method may throw an exception. Do not modify the class' + // state before it completes + $xml = $this->parseFile($this->file); + + $this->classes = array(); + + foreach ($xml->namespace as $namespace) { + $this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace)); + } + + foreach ($xml->class as $class) { + $this->classes[(string) $class['name']] = $class; + } + } + private function loadClassMetadataFromXml(ClassMetadata $metadata, \SimpleXMLElement $classDescription) { if (count($classDescription->{'group-sequence-provider'}) > 0) { diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php index cf6dd92dcad9b..9212f0d9f3c73 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php @@ -42,25 +42,7 @@ class YamlFileLoader extends FileLoader public function loadClassMetadata(ClassMetadata $metadata) { if (null === $this->classes) { - if (null === $this->yamlParser) { - $this->yamlParser = new YamlParser(); - } - - // This method may throw an exception. Do not modify the class' - // state before it completes - if (false === ($classes = $this->parseFile($this->file))) { - return false; - } - - $this->classes = $classes; - - if (isset($this->classes['namespaces'])) { - foreach ($this->classes['namespaces'] as $alias => $namespace) { - $this->addNamespaceAlias($alias, $namespace); - } - - unset($this->classes['namespaces']); - } + $this->loadClassesFromYaml(); } if (isset($this->classes[$metadata->getClassName()])) { @@ -74,6 +56,20 @@ public function loadClassMetadata(ClassMetadata $metadata) return false; } + /** + * Return the names of the classes mapped in this file. + * + * @return string[] The classes names + */ + public function getMappedClasses() + { + if (null === $this->classes) { + $this->loadClassesFromYaml(); + } + + return array_keys($this->classes); + } + /** * Parses a collection of YAML nodes. * @@ -137,12 +133,29 @@ private function parseFile($path) return $classes; } - /** - * Loads the validation metadata from the given YAML class description. - * - * @param ClassMetadata $metadata The metadata to load - * @param array $classDescription The YAML class description - */ + private function loadClassesFromYaml() + { + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + // This method may throw an exception. Do not modify the class' + // state before it completes + if (false === ($classes = $this->parseFile($this->file))) { + return; + } + + $this->classes = $classes; + + if (isset($this->classes['namespaces'])) { + foreach ($this->classes['namespaces'] as $alias => $namespace) { + $this->addNamespaceAlias($alias, $namespace); + } + + unset($this->classes['namespaces']); + } + } + private function loadClassMetadataFromYaml(ClassMetadata $metadata, array $classDescription) { if (isset($classDescription['group_sequence_provider'])) { diff --git a/src/Symfony/Component/Validator/ValidatorBuilder.php b/src/Symfony/Component/Validator/ValidatorBuilder.php index d82aca5612cdd..704d76baad0f0 100644 --- a/src/Symfony/Component/Validator/ValidatorBuilder.php +++ b/src/Symfony/Component/Validator/ValidatorBuilder.php @@ -24,11 +24,10 @@ use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Mapping\Loader\LoaderChain; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; -use Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader; use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; -use Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader; use Symfony\Component\Validator\Validator\RecursiveValidator; /** @@ -283,35 +282,40 @@ public function setTranslationDomain($translationDomain) } /** - * {@inheritdoc} + * @return LoaderInterface[] */ - public function getValidator() + public function getLoaders() { - $metadataFactory = $this->metadataFactory; + $loaders = array(); - if (!$metadataFactory) { - $loaders = array(); + foreach ($this->xmlMappings as $xmlMapping) { + $loaders[] = new XmlFileLoader($xmlMapping); + } - if (count($this->xmlMappings) > 1) { - $loaders[] = new XmlFilesLoader($this->xmlMappings); - } elseif (1 === count($this->xmlMappings)) { - $loaders[] = new XmlFileLoader($this->xmlMappings[0]); - } + foreach ($this->yamlMappings as $yamlMappings) { + $loaders[] = new YamlFileLoader($yamlMappings); + } - if (count($this->yamlMappings) > 1) { - $loaders[] = new YamlFilesLoader($this->yamlMappings); - } elseif (1 === count($this->yamlMappings)) { - $loaders[] = new YamlFileLoader($this->yamlMappings[0]); - } + foreach ($this->methodMappings as $methodName) { + $loaders[] = new StaticMethodLoader($methodName); + } - foreach ($this->methodMappings as $methodName) { - $loaders[] = new StaticMethodLoader($methodName); - } + if ($this->annotationReader) { + $loaders[] = new AnnotationLoader($this->annotationReader); + } - if ($this->annotationReader) { - $loaders[] = new AnnotationLoader($this->annotationReader); - } + return $loaders; + } + + /** + * {@inheritdoc} + */ + public function getValidator() + { + $metadataFactory = $this->metadataFactory; + if (!$metadataFactory) { + $loaders = $this->getLoaders(); $loader = null; if (count($loaders) > 1) {