Permalink
Browse files

feature #23834 [DI] Add "PHP fluent format" for configuring the conta…

…iner (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
  • Loading branch information...
nicolas-grekas committed Sep 20, 2017
2 parents 20ecf91 + 814cc31 commit 2f8647426749d018afa7390bbce54b09e27220b7
Showing with 2,048 additions and 1 deletion.
  1. +102 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php
  2. +118 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php
  3. +30 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/AliasConfigurator.php
  4. +128 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php
  5. +68 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php
  6. +36 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php
  7. +43 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php
  8. +57 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ParametersConfigurator.php
  9. +84 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php
  10. +63 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ReferenceConfigurator.php
  11. +68 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php
  12. +154 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php
  13. +35 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/AbstractTrait.php
  14. +44 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ArgumentTrait.php
  15. +36 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/AutoconfigureTrait.php
  16. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/AutowireTrait.php
  17. +43 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/BindTrait.php
  18. +34 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/CallTrait.php
  19. +32 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ClassTrait.php
  20. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ConfiguratorTrait.php
  21. +35 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DecorateTrait.php
  22. +34 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/DeprecateTrait.php
  23. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/FactoryTrait.php
  24. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/FileTrait.php
  25. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/LazyTrait.php
  26. +53 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ParentTrait.php
  27. +30 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/PropertyTrait.php
  28. +39 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/PublicTrait.php
  29. +29 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ShareTrait.php
  30. +30 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/SyntheticTrait.php
  31. +43 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/TagTrait.php
  32. +19 −1 src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php
  33. +7 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php
  34. +10 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.expected.yml
  35. +13 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php
  36. +15 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.expected.yml
  37. +24 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php
  38. +27 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.expected.yml
  39. +23 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php
  40. +21 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.expected.yml
  41. +24 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php
  42. +19 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.expected.yml
  43. +21 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php
  44. +25 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.expected.yml
  45. +24 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php
  46. +122 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php
  47. +41 −0 src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php
@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
abstract class AbstractConfigurator
{
const FACTORY = 'unknown';
public function __call($method, $args)
{
if (method_exists($this, 'set'.$method)) {
return call_user_func_array(array($this, 'set'.$method), $args);
}
throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method));
}
/**
* Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value.
*
* @param mixed $value
* @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars and arrays are
*
* @return mixed the value, optionaly cast to a Definition/Reference
*/
public static function processValue($value, $allowServices = false)
{
if (is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = static::processValue($v, $allowServices);
}
return $value;
}
if ($value instanceof ReferenceConfigurator) {
static $refCast;
if (!$refCast) {
$refCast = \Closure::bind(function ($value) {
return new Reference($value->id, $value->invalidBehavior);
}, null, $value);
}
// cast ReferenceConfigurator to Reference
return $refCast($value);
}
if ($value instanceof InlineServiceConfigurator) {
static $defCast;
if (!$defCast) {
$defCast = \Closure::bind(function ($value) {
$def = $value->definition;
$value->definition = null;
return $def;
}, null, $value);
}
// cast InlineServiceConfigurator to Definition
return $defCast($value);
}
if ($value instanceof self) {
throw new InvalidArgumentException(sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY));
}
switch (true) {
case null === $value:
case is_scalar($value):
return $value;
case $value instanceof ArgumentInterface:
case $value instanceof Definition:
case $value instanceof Expression:
case $value instanceof Parameter:
case $value instanceof Reference:
if ($allowServices) {
return $value;
}
}
throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', is_object($value) ? get_class($value) : gettype($value)));
}
}
@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
abstract class AbstractServiceConfigurator extends AbstractConfigurator
{
protected $parent;
protected $definition;
protected $id;
protected $defaultTags = array();
public function __construct(ServicesConfigurator $parent, Definition $definition, $id = null, array $defaultTags = array())
{
$this->parent = $parent;
$this->definition = $definition;
$this->id = $id;
$this->defaultTags = $defaultTags;
}
public function __destruct()
{
// default tags should be added last
foreach ($this->defaultTags as $name => $attributes) {
foreach ($attributes as $attributes) {
$this->definition->addTag($name, $attributes);
}
}
$this->defaultTags = array();
}
/**
* Registers a service.
*
* @param string $id
* @param string|null $class
*
* @return ServiceConfigurator
*/
final public function set($id, $class = null)
{
$this->__destruct();
return $this->parent->set($id, $class);
}
/**
* Creates an alias.
*
* @param string $id
* @param string $ref
*
* @return AliasConfigurator
*/
final public function alias($id, $referencedId)
{
$this->__destruct();
return $this->parent->alias($id, $referencedId);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*
* @param string $namespace
* @param string $resource
*
* @return PrototypeConfigurator
*/
final public function load($namespace, $resource)
{
$this->__destruct();
return $this->parent->load($namespace, $resource);
}
/**
* Gets an already defined service definition.
*
* @param string $id
*
* @return ServiceConfigurator
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
final public function get($id)
{
$this->__destruct();
return $this->parent->get($id);
}
/**
* Registers a service.
*
* @param string $id
* @param string|null $class
*
* @return ServiceConfigurator
*/
final public function __invoke($id, $class = null)
{
$this->__destruct();
return $this->parent->set($id, $class);
}
}
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Alias;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class AliasConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'alias';
use Traits\PublicTrait;
public function __construct(ServicesConfigurator $parent, Alias $alias)
{
$this->parent = $parent;
$this->definition = $alias;
}
}
@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ContainerConfigurator extends AbstractConfigurator
{
const FACTORY = 'container';
private $container;
private $loader;
private $instanceof;
private $path;
private $file;
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, &$instanceof, $path, $file)
{
$this->container = $container;
$this->loader = $loader;
$this->instanceof = &$instanceof;
$this->path = $path;
$this->file = $file;
}
final public function extension($namespace, array $config)
{
if (!$this->container->hasExtension($namespace)) {
$extensions = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
throw new InvalidArgumentException(sprintf(
'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s',
$namespace,
$this->file,
$namespace,
$extensions ? sprintf('"%s"', implode('", "', $extensions)) : 'none'
));
}
$this->container->loadFromExtension($namespace, static::processValue($config));
}
final public function import($resource, $type = null, $ignoreErrors = false)
{
$this->loader->setCurrentDir(dirname($this->path));
$this->loader->import($resource, $type, $ignoreErrors, $this->file);
}
/**
* @return ParametersConfigurator
*/
public function parameters()
{
return new ParametersConfigurator($this->container);
}
/**
* @return ServicesConfigurator
*/
public function services()
{
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof);
}
}
/**
* Creates a service reference.
*
* @param string $id
*
* @return ReferenceConfigurator
*/
function ref($id)
{
return new ReferenceConfigurator($id);
}
/**
* Creates an inline service.
*
* @param string|null $class
*
* @return InlineServiceConfigurator
*/
function inline($class = null)
{
return new InlineServiceConfigurator(new Definition($class));
}
/**
* Creates a lazy iterator.
*
* @param ReferenceConfigurator[] $values
*
* @return IteratorArgument
*/
function iterator(array $values)
{
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
}
/**
* Creates an expression.
*
* @param string $expression an expression
*
* @return Expression
*/
function expr($expression)
{
return new Expression($expression);
}
Oops, something went wrong.

0 comments on commit 2f86474

Please sign in to comment.