Skip to content

Commit

Permalink
feature #27075 [DI][DX] Allow exclude to be an array of patterns (mag…
Browse files Browse the repository at this point in the history
…netik)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[DI][DX] Allow exclude to be an array of patterns

| Q             | A
| ------------- | ---
| Branch?       | 4.2
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #23956
| License       | MIT

This is basically continuing #24428.

In YAML before:
```yaml
AppBundle\:
  resource: '../../src/AppBundle/*'
  exclude: '../../src/AppBundle/{Entity,Payload,Repository}'
```

in YAML after:

```yaml
AppBundle\:
  resource: '../../src/AppBundle/*'
  exclude:
    - '../../src/AppBundle/{Entity,Payload,Repository}'
    - '../../src/AppBundle/Event/*Event.php'
```

In XML before:
```xml
<prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}" />
```

in XML after:

```xml
<prototype namespace="App\" resource="../src/*">
  <exclude>../src/{Entity,Migrations,Tests}</exclude>
  <exclude>../src/Yolo</exclude>
</prototype>
```

In PHP before:
```php
$di->load(Prototype::class.'\\', '../Prototype')
        ->autoconfigure()
        ->exclude('../Prototype/{OtherDir,BadClasses}')
```

In PHP after:
```php
    $di->load(Prototype::class.'\\', '../Prototype')
        ->autoconfigure()
        ->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses'])
```

Everything is backward compatible.
Maybe a decision about handling both attribute exclude and element exclude in XML should be taken.

Commits
-------

3ae3a03 [DI][DX] Allow exclude to be an array of patterns (taking #24428 over)
  • Loading branch information
fabpot committed May 9, 2018
2 parents cb2a77b + 3ae3a03 commit 226e2f3
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 14 deletions.
Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
16 changes: 8 additions & 8 deletions src/Symfony/Component/DependencyInjection/Loader/FileLoader.php
Expand Up @@ -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)
{
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Expand Up @@ -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 <exclude> 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);
}
Expand Down
Expand Up @@ -160,6 +160,7 @@
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="namespace" type="xsd:string" use="required" />
<xsd:attribute name="resource" type="xsd:string" use="required" />
Expand Down
@@ -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
@@ -0,0 +1,22 @@
<?php

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;

return function (ContainerConfigurator $c) {
$di = $c->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);
};
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\" resource="../Prototype/*">
<exclude>../Prototype/OtherDir</exclude>
<exclude>../Prototype/BadClasses</exclude>
</prototype>
</services>
</container>
Expand Up @@ -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();
Expand Down
Expand Up @@ -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
Expand Down

0 comments on commit 226e2f3

Please sign in to comment.