8000 feature #23834 [DI] Add "PHP fluent format" for configuring the conta… · symfony/symfony@2f86474 · GitHub
[go: up one dir, main page]

Skip to content
10000

Commit 2f86474

Browse files
feature #23834 [DI] Add "PHP fluent format" for configuring the container (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [DI] Add "PHP fluent format" for configuring the container | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #22407 | License | MIT | Doc PR | - This PR allows one to write DI configuration using just PHP, with full IDE auto-completion. Example: ```php namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo; return function (ContainerConfigurator $c) { $c->import('basic.php'); $s = $c->services()->defaults() ->public() ->private() ->autoconfigure() ->autowire() ->tag('t', array('a' => 'b')) ->bind(Foo::class, ref('bar')) ->private(); $s->set(Foo::class)->args([ref('bar')])->public(); $s->set('bar', Foo::class)->call('setFoo')->autoconfigure(false); }; ``` Commits ------- 814cc31 [DI] Add "PHP fluent format" for configuring the container
2 parents 20ecf91 + 814cc31 commit 2f86474

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2048
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
17+
use Symfony\Component\DependencyInjection\Parameter;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
use Symfony\Component\ExpressionLanguage\Expression;
20+
21+
abstract class AbstractConfigurator
22+
{
23+
const FACTORY = 'unknown';
24+
25+
public function __call($method, $args)
26+
{
27+
if (method_exists($this, 'set'.$method)) {
28+
return call_user_func_array(array($this, 'set'.$method), $args);
29+
}
30+
31+
throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method));
32+
}
33+
34+
/**
35+
* Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value.
36+
*
37+
* @param mixed $value
38+
* @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars and arrays are
39+
*
40+
* @return mixed the value, optionaly cast to a Definition/Reference
41+
*/
42+
public static function processValue($value, $allowServices = false)
43+
{
44+
if (is_array($value)) {
45+
foreach ($value as $k => $v) {
46+
$value[$k] = static::processValue($v, $allowServices);
47+
}
48+
49+
return $value;
50+
}
51+
52+
if ($value instanceof ReferenceConfigurator) {
53+
static $refCast;
54+
55+
if (!$refCast) {
56+
$refCast = \Closure::bind(function ($value) {
57+
return new Reference($value->id, $value->invalidBehavior);
58+
}, null, $value);
59+
}
60+
61+
// cast ReferenceConfigurator to Reference
62+
return $refCast($value);
63+
}
64+
65+
if ($value instanceof InlineServiceConfigurator) {
66+
static $defCast;
67+
68+
if (!$defCast) {
69+
$defCast = \Closure::bind(function ($value) {
70+
$def = $value->definition;
71+
$value->definition = null;
72+
73+
return $def;
74+
}, null, $value);
75+
}
76+
77+
// cast InlineServiceConfigurator to Definition
78+
return $defCast($value);
79+
}
80+
81+
if ($value instanceof self) {
82+
throw new InvalidArgumentException(sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY));
83+
}
84+
85+
switch (true) {
86+
case null === $value:
87+
case is_scalar($value):
88+
return $value;
89+
90+
case $value instanceof ArgumentInterface:
91+
case $value instanceof Definition:
92+
case $value instanceof Expression:
93+
case $value instanceof Parameter:
94+
case $value instanceof Reference:
95+
if ($allowServices) {
96+
return $value;
97+
}
98+
}
99+
100+
throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', is_object($value) ? get_class($value) : gettype($value)));
101+
}
102+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
16+
17+
abstract class AbstractServiceConfigurator extends AbstractConfigurator
18+
{
19+
protected $parent;
20+
protected $definition;
21+
protected $id;
22+
protected $defaultTags = array();
23+
24+
public function __construct(ServicesConfigurator $parent, Definition $definition, $id = null, array $defaultTags = array())
25+
{
26+
$this->parent = $parent;
27+
$this->definition = $definition;
28+
$this->id = $id;
29+
$this->defaultTags = $defaultTags;
30+
}
31+
32+
public function __destruct()
33+
{
34+
// default tags should be added last
35+
foreach ($this->defaultTags as $name => $attributes) {
36+
foreach ($attributes as $attributes) {
37+
$this->definition->addTag($name, $attributes);
38+
}
39+
}
40+
$this->defaultTags = array();
41+
}
42+
43+
/**
44+
* Registers a service.
45+
*
46+
* @param string $id
47+
* @param string|null $class
48+
*
49+
* @return ServiceConfigurator
50+
*/
51+
final public function set($id, $class = null)
52+
{
53+
$this->__destruct();
54+
55+
return $this->parent->set($id, $class);
56+
}
57+
58+
/**
59+
* Creates an alias.
60+
*
61+
* @param string $id
62+
* @param string $ref
63+
*
64+
* @return AliasConfigurator
65+
*/
66+
final public function alias($id, $referencedId)
67+
{
68+
$this->__destruct();
69+
70+
return $this->parent->alias($id, $referencedId);
71+
}
72+
73+
/**
74+
* Registers a PSR-4 namespace using a glob pattern.
75+
*
76+
* @param string $namespace
77+
* @param string $resource
78+
*
79+
* @return PrototypeConfigurator
80+
*/
81+
final public function load($namespace, $resource)
82+
{
83+
$this->__destruct();
84+
85+
return $this->parent->load($namespace, $resource);
86+
}
87+
88+
/**
89+
* Gets an already defined service definition.
90+
*
91+
* @param string $id
92+
*
93+
* @return ServiceConfigurator
94+
*
95+
* @throws ServiceNotFoundException if the service definition does not exist
96+
*/
97+
final public function get($id)
98+
{
99+
$this->__destruct();
100+
101+
return $this->parent->get($id);
102+
}
103+
104+
/**
105+
* Registers a service.
106+
*
107+
* @param string $id
108+
* @param string|null $class
109+
*
110+
* @return ServiceConfigurator
111+
*/
112+
final public function __invoke($id, $class = null)
113+
{
114+
$this->__destruct();
115+
116+
return $this->parent->set($id, $class);
117+
}
118+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\DependencyInjection\Alias;
15+
16+
/**
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
class AliasConfigurator extends AbstractServiceConfigurator
20+
{
21+
const FACTORY = 'alias';
22+
23+
use Traits\PublicTrait;
24+
25+
public function __construct(ServicesConfigurator $parent, Alias $alias)
26+
{
27+
$this->parent = $parent;
28+
$this->definition = $alias;
29+
}
30+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
19+
use Symfony\Component\ExpressionLanguage\Expression;
20+
21+
/**
22+
* @author Nicolas Grekas <p@tchwork.com>
23+
*/
24+
class ContainerConfigurator extends AbstractConfigurator
25+
{
26+
const FACTORY = 'container';
27+
28+
private $container;
29+
private $loader;
30+
private $instanceof;
31+
private $path;
32+
private $file;
33+
34+
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, &$instanceof, $path, $file)
35+
{
36+
$this->container = $container;
37+
$this->loader = $loader;
38+
$this->instanceof = &$instanceof;
39< F438 code class="diff-text syntax-highlighted-line addition">+
$this->path = $path;
40+
$this->file = $file;
41+
}
42+
43+
final public function extension($namespace, array $config)
44+
{
45+
if (!$this->container->hasExtension($namespace)) {
46+
$extensions = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
47+
throw new InvalidArgumentException(sprintf(
48+
'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s',
49+
$namespace,
50+
$this->file,
51+
$namespace,
52+
$extensions ? sprintf('"%s"', implode('", "', $extensions)) : 'none'
53+
));
54+
}
55+
56+
$this->container->loadFromExtension($namespace, static::processValue($config));
57+
}
58+
59+
final public function import($resource, $type = null, $ignoreErrors = false)
60+
{
61+
$this->loader->setCurrentDir(dirname($this->path));
62+
$this->loader->import($resource, $type, $ignoreErrors, $this->file);
63+
}
64+
65+
/**
66+
* @return ParametersConfigurator
67+
*/
68+
public function parameters()
69+
{
70+
return new ParametersConfigurator($this->container);
71+
}
72+
73+
/**
74+
* @return ServicesConfigurator
75+
*/
76+
public function services()
77+
{
78+
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof);
79+
}
80+
}
81+
82+
/**
83+
* Creates a service reference.
84+
*
85+
* @param string $id
86+
*
87+
* @return ReferenceConfigurator
88+
*/
89+
function ref($id)
90+
{
91+
return new ReferenceConfigurator($id);
92+
}
93+
94+
/**
95+
* Creates an inline service.
96+
*
97+
* @param string|null $class
98+
*
99+
* @return InlineServiceConfigurator
100+
*/
101+
function inline($class = null)
102+
{
103+
return new InlineServiceConfigurator(new Definition($class));
104+
}
105+
106+
/**
107+
* Creates a lazy iterator.
108+
*
109+
* @param ReferenceConfigurator[] $values
110+
*
111+
* @return IteratorArgument
112+
*/
113+
function iterator(array $values)
114+
{
115+
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
116+
}
117+
118+
/**
119+
* Creates an expression.
120+
*
121+
* @param string $expression an expression
122+
*
123+
* @return Expression
124+
*/
125+
function expr($expression)
126+
{
127+
return new Expression($expression);
128+
}

0 commit comments

Comments
 (0)
0