From 3ae3a03f41c34d6224cd9787c8ca111f7a8a71a4 Mon Sep 17 00:00:00 2001 From: Baptiste Lafontaine Date: Fri, 27 Apr 2018 15:39:41 +0200 Subject: [PATCH] [DI][DX] Allow exclude to be an array of patterns (taking #24428 over) --- .../Configurator/PrototypeConfigurator.php | 12 +++++---- .../DependencyInjection/Loader/FileLoader.php | 16 ++++++------ .../Loader/XmlFileLoader.php | 9 ++++++- .../schema/dic/services/services-1.0.xsd | 1 + .../config/prototype_array.expected.yml | 25 +++++++++++++++++++ .../Tests/Fixtures/config/prototype_array.php | 22 ++++++++++++++++ .../Fixtures/xml/services_prototype_array.xml | 9 +++++++ .../Tests/Loader/FileLoaderTest.php | 19 ++++++++++++++ .../Tests/Loader/XmlFileLoaderTest.php | 20 +++++++++++++++ 9 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php index 573dcc51e820..45e88a0bfaef 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php @@ -39,7 +39,7 @@ class PrototypeConfigurator extends AbstractServiceConfigurator private $loader; private $resource; - private $exclude; + private $excludes; private $allowParent; public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) @@ -63,19 +63,21 @@ public function __destruct() parent::__destruct(); if ($this->loader) { - $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->exclude); + $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes); } $this->loader = null; } /** - * Excludes files from registration using a glob pattern. + * Excludes files from registration using glob patterns. + * + * @param string[]|string $excludes * * @return $this */ - final public function exclude(string $exclude) + final public function exclude($excludes) { - $this->exclude = $exclude; + $this->excludes = (array) $excludes; return $this; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 83a3f4f87ca4..b193354a6eae 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -40,10 +40,10 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l /** * Registers a set of classes as services using PSR-4 for discovery. * - * @param Definition $prototype A definition to use as template - * @param string $namespace The namespace prefix of classes in the scanned directory - * @param string $resource The directory to look for classes, glob-patterns allowed - * @param string $exclude A globed path of files to exclude + * @param Definition $prototype A definition to use as template + * @param string $namespace The namespace prefix of classes in the scanned directory + * @param string $resource The directory to look for classes, glob-patterns allowed + * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude */ public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null) { @@ -54,7 +54,7 @@ public function registerClasses(Definition $prototype, $namespace, $resource, $e throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: %s.', $namespace)); } - $classes = $this->findClasses($namespace, $resource, $exclude); + $classes = $this->findClasses($namespace, $resource, (array) $exclude); // prepare for deep cloning $serializedPrototype = serialize($prototype); $interfaces = array(); @@ -101,14 +101,14 @@ protected function setDefinition($id, Definition $definition) } } - private function findClasses($namespace, $pattern, $excludePattern) + private function findClasses($namespace, $pattern, array $excludePatterns) { $parameterBag = $this->container->getParameterBag(); $excludePaths = array(); $excludePrefix = null; - if ($excludePattern) { - $excludePattern = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePattern)); + $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); + foreach ($excludePatterns as $excludePattern) { foreach ($this->glob($excludePattern, true, $resource) as $path => $info) { if (null === $excludePrefix) { $excludePrefix = $resource->getPrefix(); diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 3b1c3548f6d5..9fbff021d680 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -146,7 +146,14 @@ private function parseDefinitions(\DOMDocument $xml, $file, $defaults) foreach ($services as $service) { if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) { if ('prototype' === $service->tagName) { - $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), (string) $service->getAttribute('exclude')); + $excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue'); + if ($service->hasAttribute('exclude')) { + if (count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = array($service->getAttribute('exclude')); + } + $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes); } else { $this->setDefinition((string) $service->getAttribute('id'), $definition); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 60a01bd666ae..015f81f9536e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -160,6 +160,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml new file mode 100644 index 000000000000..e8a03691c95c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml @@ -0,0 +1,25 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo + public: true + tags: + - { name: foo } + - { name: baz } + deprecated: "%service_id%" + arguments: [1] + factory: f + Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar + public: true + tags: + - { name: foo } + - { name: baz } + deprecated: "%service_id%" + lazy: true + arguments: [1] + factory: f diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php new file mode 100644 index 000000000000..4d5625c245aa --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php @@ -0,0 +1,22 @@ +services()->defaults() + ->tag('baz'); + $di->load(Prototype::class.'\\', '../Prototype') + ->autoconfigure() + ->exclude(array('../Prototype/OtherDir', '../Prototype/BadClasses')) + ->factory('f') + ->deprecate('%service_id%') + ->args(array(0)) + ->args(array(1)) + ->autoconfigure(false) + ->tag('foo') + ->parent('foo'); + $di->set('foo')->lazy()->abstract(); + $di->get(Prototype\Foo::class)->lazy(false); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml new file mode 100644 index 000000000000..892e0a7e8c95 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype_array.xml @@ -0,0 +1,9 @@ + + + + + ../Prototype/OtherDir + ../Prototype/BadClasses + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 8a271a818a47..a15b1b031510 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -136,6 +136,25 @@ public function testRegisterClassesWithExclude() ); } + public function testRegisterClassesWithExcludeAsArray() + { + $container = new ContainerBuilder(); + $container->setParameter('sub_dir', 'Sub'); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + $loader->registerClasses( + new Definition(), + 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', + 'Prototype/*', array( + 'Prototype/%sub_dir%', + 'Prototype/OtherDir/AnotherSub/DeeperBaz.php', + ) + ); + $this->assertTrue($container->has(Foo::class)); + $this->assertTrue($container->has(Baz::class)); + $this->assertFalse($container->has(Bar::class)); + $this->assertFalse($container->has(DeeperBaz::class)); + } + public function testNestedRegisterClasses() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 7e0ce3871909..2e87291bf2c1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -617,6 +617,26 @@ public function testPrototype() $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar', $resources); } + public function testPrototypeExcludeWithArray() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services_prototype_array.xml'); + + $ids = array_keys($container->getDefinitions()); + sort($ids); + $this->assertSame(array(Prototype\Foo::class, Prototype\Sub\Bar::class, 'service_container'), $ids); + + $resources = $container->getResources(); + + $fixturesDir = dirname(__DIR__).DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR; + $this->assertTrue(false !== array_search(new FileResource($fixturesDir.'xml'.DIRECTORY_SEPARATOR.'services_prototype_array.xml'), $resources)); + $this->assertTrue(false !== array_search(new GlobResource($fixturesDir.'Prototype', '/*', true), $resources)); + $resources = array_map('strval', $resources); + $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo', $resources); + $this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar', $resources); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException * @expectedExceptionMessage Invalid attribute "class" defined for alias "bar" in