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
This commit is contained in:
Nicolas Grekas 2017-09-20 15:06:57 +02:00
commit 2f86474267
47 changed files with 2048 additions and 1 deletions

View File

@ -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)));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,68 @@
<?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\Exception\InvalidArgumentException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @method InstanceofConfigurator instanceof(string $fqcn)
*/
class DefaultsConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'defaults';
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\PublicTrait;
/**
* Adds a tag for this definition.
*
* @param string $name The tag name
* @param array $attributes An array of attributes
*
* @return $this
*
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
*/
final public function tag($name, array $attributes = array())
{
if (!is_string($name) || '' === $name) {
throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string.'));
}
foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute));
}
}
$this->definition->addTag($name, $attributes);
return $this;
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*
* @param string $fqcn
*
* @return InstanceofConfigurator
*/
final protected function setInstanceof($fqcn)
{
return $this->parent->instanceof($fqcn);
}
}

View File

@ -0,0 +1,36 @@
<?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;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class InlineServiceConfigurator extends AbstractConfigurator
{
const FACTORY = 'inline';
use Traits\ArgumentTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\TagTrait;
public function __construct(Definition $definition)
{
$this->definition = $definition;
}
}

View File

@ -0,0 +1,43 @@
<?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;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @method InstanceofConfigurator instanceof(string $fqcn)
*/
class InstanceofConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'instanceof';
use Traits\AutowireTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\LazyTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*
* @param string $fqcn
*
* @return InstanceofConfigurator
*/
final protected function setInstanceof($fqcn)
{
return $this->parent->instanceof($fqcn);
}
}

View File

@ -0,0 +1,57 @@
<?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\ContainerBuilder;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ParametersConfigurator extends AbstractConfigurator
{
const FACTORY = 'parameters';
private $container;
public function __construct(ContainerBuilder $container)
{
$this->container = $container;
}
/**
* Creates a parameter.
*
* @param string $name
* @param string $value
*
* @return $this
*/
final public function set($name, $value)
{
$this->container->setParameter($name, static::processValue($value, true));
return $this;
}
/**
* Creates a parameter.
*
* @param string $name
* @param string $value
*
* @return $this
*/
final public function __invoke($name, $value)
{
return $this->set($name, $value);
}
}

View File

@ -0,0 +1,84 @@
<?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\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class PrototypeConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'load';
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
private $loader;
private $resource;
private $exclude;
private $allowParent;
public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, $namespace, $resource, $allowParent)
{
$definition = new Definition();
$definition->setPublic($defaults->isPublic());
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
$definition->setBindings($defaults->getBindings());
$definition->setChanges(array());
$this->loader = $loader;
$this->resource = $resource;
$this->allowParent = $allowParent;
parent::__construct($parent, $definition, $namespace, $defaults->getTags());
}
public function __destruct()
{
parent::__destruct();
if ($this->loader) {
$this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->exclude);
}
$this->loader = null;
}
/**
* Excludes files from registration using a glob pattern.
*
* @param string $exclude
*
* @return $this
*/
final public function exclude($exclude)
{
$this->exclude = $exclude;
return $this;
}
}

View File

@ -0,0 +1,63 @@
<?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\ContainerInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ReferenceConfigurator
{
private $id;
private $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
public function __construct($id)
{
$this->id = $id;
}
/**
* @return $this
*/
final public function ignoreOnInvalid()
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function nullOnInvalid()
{
$this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function ignoreOnUninitialized()
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
return $this;
}
public function __toString()
{
return $this->id;
}
}

View File

@ -0,0 +1,68 @@
<?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\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'services';
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ClassTrait;
use Traits\ConfiguratorTrait;
use Traits\DecorateTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\SyntheticTrait;
use Traits\TagTrait;
private $container;
private $instanceof;
private $allowParent;
public function __construct(ContainerBuilder $container, array $instanceof, $allowParent, ServicesConfigurator $parent, Definition $definition, $id, array $defaultTags)
{
$this->container = $container;
$this->instanceof = $instanceof;
$this->allowParent = $allowParent;
parent::__construct($parent, $definition, $id, $defaultTags);
}
public function __destruct()
{
parent::__destruct();
if (!$this->definition instanceof ChildDefinition) {
$this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof));
} else {
$this->container->setDefinition($this->id, $this->definition);
}
}
}

View File

@ -0,0 +1,154 @@
<?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;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @method InstanceofConfigurator instanceof($fqcn)
*/
class ServicesConfigurator extends AbstractConfigurator
{
const FACTORY = 'services';
private $defaults;
private $container;
private $loader;
private $instanceof;
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof)
{
$this->defaults = new Definition();
$this->container = $container;
$this->loader = $loader;
$this->instanceof = &$instanceof;
$instanceof = array();
}
/**
* Defines a set of defaults for following service definitions.
*
* @return DefaultsConfigurator
*/
public function defaults()
{
return new DefaultsConfigurator($this, $this->defaults = new Definition());
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*
* @param string $fqcn
*
* @return InstanceofConfigurator
*/
final protected function setInstanceof($fqcn)
{
$this->instanceof[$fqcn] = $definition = new ChildDefinition('');
return new InstanceofConfigurator($this, $definition, $fqcn);
}
/**
* Registers a service.
*
* @param string $id
* @param string|null $class
*
* @return ServiceConfigurator
*/
final public function set($id, $class = null)
{
$defaults = $this->defaults;
$allowParent = !$defaults->getChanges() && empty($this->instanceof);
$definition = new Definition();
$definition->setPublic($defaults->isPublic());
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
$definition->setBindings($defaults->getBindings());
$definition->setChanges(array());
$configurator = new ServiceConfigurator($this->container, $this->instanceof, $allowParent, $this, $definition, $id, $defaults->getTags());
return null !== $class ? $configurator->class($class) : $configurator;
}
/**
* Creates an alias.
*
* @param string $id
* @param string $referencedId
*
* @return AliasConfigurator
*/
final public function alias($id, $referencedId)
{
$ref = static::processValue($referencedId, true);
$alias = new Alias((string) $ref, $this->defaults->isPublic());
$this->container->setAlias($id, $alias);
return new AliasConfigurator($this, $alias);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*
* @param string $namespace
* @param string $resource
*
* @return PrototypeConfigurator
*/
final public function load($namespace, $resource)
{
$allowParent = !$this->defaults->getChanges() && empty($this->instanceof);
return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, $allowParent);
}
/**
* 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)
{
$allowParent = !$this->defaults->getChanges() && empty($this->instanceof);
$definition = $this->container->getDefinition($id);
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), $allowParent, $this, $definition, $id, array());
}
/**
* Registers a service.
*
* @param string $id
* @param string|null $class
*
* @return ServiceConfigurator
*/
final public function __invoke($id, $class = null)
{
return $this->set($id, $class);
}
}

View File

@ -0,0 +1,35 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Definition;
/**
* @method $this abstract(bool $abstract = true)
*/
trait AbstractTrait
{
/**
* Whether this definition is abstract, that means it merely serves as a
* template for other definitions.
*
* @param bool $abstract
*
* @return $this
*/
final protected function setAbstract($abstract = true)
{
$this->definition->setAbstract($abstract);
return $this;
}
}

View File

@ -0,0 +1,44 @@
<?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\Traits;
trait ArgumentTrait
{
/**
* Sets the arguments to pass to the service constructor/factory method.
*
* @param array $arguments An array of arguments
*
* @return $this
*/
final public function args(array $arguments)
{
$this->definition->setArguments(static::processValue($arguments, true));
return $this;
}
/**
* Sets one argument to pass to the service constructor/factory method.
*
* @param string|int $key
* @param mixed $value
*
* @return $this
*/
final public function arg($key, $value)
{
$this->definition->setArgument($key, static::processValue($value, true));
return $this;
}
}

View File

@ -0,0 +1,36 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait AutoconfigureTrait
{
/**
* Sets whether or not instanceof conditionals should be prepended with a global set.
*
* @param bool $autoconfigured
*
* @return $this
*
* @throws InvalidArgumentException when a parent is already set
*/
final public function autoconfigure($autoconfigured = true)
{
if ($autoconfigured && $this->definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id));
}
$this->definition->setAutoconfigured($autoconfigured);
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait AutowireTrait
{
/**
* Enables/disables autowiring.
*
* @param bool $autowired
*
* @return $this
*/
final public function autowire($autowired = true)
{
$this->definition->setAutowired($autowired);
return $this;
}
}

View File

@ -0,0 +1,43 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
trait BindTrait
{
/**
* Sets bindings.
*
* Bindings map $named or FQCN arguments to values that should be
* injected in the matching parameters (of the constructor, of methods
* called and of controller actions).
*
* @param string $nameOrFqcn A parameter name with its "$" prefix, or a FQCN
* @param mixed $valueOrRef The value or reference to bind
*
* @return $this
*/
final public function bind($nameOrFqcn, $valueOrRef)
{
$valueOrRef = static::processValue($valueOrRef, true);
if (isset($nameOrFqcn[0]) && '$' !== $nameOrFqcn[0] && !$valueOrRef instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid binding for service "%s": named arguments must start with a "$", and FQCN must map to references. Neither applies to binding "%s".', $this->id, $nameOrFqcn));
}
$bindings = $this->definition->getBindings();
$bindings[$nameOrFqcn] = $valueOrRef;
$this->definition->setBindings($bindings);
return $this;
}
}

View File

@ -0,0 +1,34 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait CallTrait
{
/**
* Adds a method to call after service initialization.
*
* @param string $method The method name to call
* @param array $arguments An array of arguments to pass to the method call
*
* @return $this
*
* @throws InvalidArgumentException on empty $method param
*/
final public function call($method, array $arguments = array())
{
$this->definition->addMethodCall($method, static::processValue($arguments, true));
return $this;
}
}

View File

@ -0,0 +1,32 @@
<?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\Traits;
/**
* @method $this class(string $class)
*/
trait ClassTrait
{
/**
* Sets the service class.
*
* @param string $class The service class
*
* @return $this
*/
final protected function setClass($class)
{
$this->definition->setClass($class);
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait ConfiguratorTrait
{
/**
* Sets a configurator to call after the service is fully initialized.
*
* @param string|array $configurator A PHP callable reference
*
* @return $this
*/
final public function configurator($configurator)
{
$this->definition->setConfigurator(static::processValue($configurator, true));
return $this;
}
}

View File

@ -0,0 +1,35 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DecorateTrait
{
/**
* Sets the service that this service is decorating.
*
* @param null|string $id The decorated service id, use null to remove decoration
* @param null|string $renamedId The new decorated service id
* @param int $priority The priority of decoration
*
* @return $this
*
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
*/
final public function decorate($id, $renamedId = null, $priority = 0)
{
$this->definition->setDecoratedService($id, $renamedId, $priority);
return $this;
}
}

View File

@ -0,0 +1,34 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DeprecateTrait
{
/**
* Whether this definition is deprecated, that means it should not be called anymore.
*
* @param string $template Template message to use if the definition is deprecated
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
final public function deprecate($template = null)
{
$this->definition->setDeprecated(true, $template);
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait FactoryTrait
{
/**
* Sets a factory.
*
* @param string|array $factory A PHP callable reference
*
* @return $this
*/
final public function factory($factory)
{
$this->definition->setFactory(static::processValue($factory, true));
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait FileTrait
{
/**
* Sets a file to require before creating the service.
*
* @param string $file A full pathname to include
*
* @return $this
*/
final public function file($file)
{
$this->definition->setFile($file);
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait LazyTrait
{
/**
* Sets the lazy flag of this service.
*
* @param bool $lazy
*
* @return $this
*/
final public function lazy($lazy = true)
{
$this->definition->setLazy($lazy);
return $this;
}
}

View File

@ -0,0 +1,53 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* @method $this parent(string $parent)
*/
trait ParentTrait
{
/**
* Sets the Definition to inherit from.
*
* @param string $parent
*
* @return $this
*
* @throws InvalidArgumentException when parent cannot be set
*/
final protected function setParent($parent)
{
if (!$this->allowParent) {
throw new InvalidArgumentException(sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id));
}
if ($this->definition instanceof ChildDefinition) {
$this->definition->setParent($parent);
} elseif ($this->definition->isAutoconfigured()) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id));
} else {
// cast Definition to ChildDefinition
$definition = serialize($this->definition);
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
$definition = unserialize($definition);
$this->definition = $definition->setParent($parent);
}
return $this;
}
}

View File

@ -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\Traits;
trait PropertyTrait
{
/**
* Sets a specific property.
*
* @param string $name
* @param mixed $value
*
* @return $this
*/
final public function property($name, $value)
{
$this->definition->setProperty($name, static::processValue($value, true));
return $this;
}
}

View File

@ -0,0 +1,39 @@
<?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\Traits;
/**
* @method $this public()
* @method $this private()
*/
trait PublicTrait
{
/**
* @return $this
*/
final protected function setPublic()
{
$this->definition->setPublic(true);
return $this;
}
/**
* @return $this
*/
final protected function setPrivate()
{
$this->definition->setPublic(false);
return $this;
}
}

View File

@ -0,0 +1,29 @@
<?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\Traits;
trait ShareTrait
{
/**
* Sets if the service must be shared or not.
*
* @param bool $shared Whether the service must be shared or not
*
* @return $this
*/
final public function share($shared = true)
{
$this->definition->setShared($shared);
return $this;
}
}

View File

@ -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\Traits;
trait SyntheticTrait
{
/**
* Sets whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
* @param bool $synthetic
*
* @return $this
*/
final public function synthetic($synthetic = true)
{
$this->definition->setSynthetic($synthetic);
return $this;
}
}

View File

@ -0,0 +1,43 @@
<?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\Traits;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait TagTrait
{
/**
* Adds a tag for this definition.
*
* @param string $name The tag name
* @param array $attributes An array of attributes
*
* @return $this
*/
final public function tag($name, array $attributes = array())
{
if (!is_string($name) || '' === $name) {
throw new InvalidArgumentException(sprintf('The tag name for service "%s" must be a non-empty string.', $this->id));
}
foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute));
}
}
$this->definition->addTag($name, $attributes);
return $this;
}
}

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
/**
* PhpFileLoader loads service definitions from a PHP file.
*
@ -34,7 +36,16 @@ class PhpFileLoader extends FileLoader
$this->setCurrentDir(dirname($path));
$this->container->fileExists($path);
include $path;
// the closure forbids access to the private scope in the included file
$load = \Closure::bind(function ($path) use ($container, $loader, $resource, $type) {
return include $path;
}, $this, ProtectedPhpFileLoader::class);
$callback = $load($path);
if ($callback instanceof \Closure) {
$callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this);
}
}
/**
@ -53,3 +64,10 @@ class PhpFileLoader extends FileLoader
return 'php' === $type;
}
}
/**
* @internal
*/
final class ProtectedPhpFileLoader extends PhpFileLoader
{
}

View File

@ -4,4 +4,11 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
class Foo
{
public function __construct($bar = null)
{
}
function setFoo(self $foo)
{
}
}

View File

@ -0,0 +1,10 @@
services:
service_container:
class: Symfony\Component\DependencyInjection\ContainerInterface
public: true
synthetic: true
App\BarService:
class: App\BarService
public: true
arguments: [!service { class: FooClass }]

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\BarService;
return function (ContainerConfigurator $c) {
$s = $c->services();
$s->set(BarService::class)
->args(array(inline('FooClass')));
};

View File

@ -0,0 +1,15 @@
services:
service_container:
class: Symfony\Component\DependencyInjection\ContainerInterface
public: true
synthetic: true
foo:
class: Class2
public: true
file: file.php
lazy: true
arguments: [!service { class: Class1, public: false }]
bar:
alias: foo
public: true

View File

@ -0,0 +1,24 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\BarService;
return function (ContainerConfigurator $c) {
$c->services()
->set('bar', 'Class1')
->set(BarService::class)
->abstract(true)
->lazy()
->set('foo')
->parent(BarService::class)
->decorate('bar', 'b', 1)
->args(array(ref('b')))
->class('Class2')
->file('file.php')
->parent('bar')
->parent(BarService::class)
;
};

View File

@ -0,0 +1,27 @@
services:
service_container:
class: Symfony\Component\DependencyInjection\ContainerInterface
public: true
synthetic: true
App\BarService:
class: App\BarService
public: true
arguments: [!service { class: FooClass }]
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo:
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo
public: true
tags:
- { name: t, a: b }
autowire: true
autoconfigure: true
arguments: ['@bar']
bar:
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo
public: false
tags:
- { name: t, a: b }
autowire: true
calls:
- [setFoo, ['@bar']]

View File

@ -0,0 +1,23 @@
<?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(array(ref('bar')))->public();
$s->set('bar', Foo::class)->call('setFoo')->autoconfigure(false);
};

View File

@ -0,0 +1,21 @@
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: tag, k: v }
lazy: true
properties: { p: 1 }
calls:
- [setFoo, ['@foo']]
shared: false
configurator: c
foo:
class: App\FooService
public: true

View File

@ -0,0 +1,24 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\FooService;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
return function (ContainerConfigurator $c) {
$s = $c->services();
$s->instanceof(Prototype\Foo::class)
->property('p', 0)
->call('setFoo', array(ref('foo')))
->tag('tag', array('k' => 'v'))
->share(false)
->lazy()
->configurator('c')
->property('p', 1);
$s->load(Prototype::class.'\\', '../Prototype')->exclude('../Prototype/*/*');
$s->set('foo', FooService::class);
};

View File

@ -0,0 +1,19 @@
parameters:
foo: Foo
bar: Bar
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
arguments: ['@bar']
bar:
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo
public: true
calls:
- [setFoo, { }]

View File

@ -0,0 +1,21 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo;
return function (ContainerConfigurator $c) {
$c->parameters()
('foo', 'Foo')
('bar', 'Bar')
;
$c->services()
(Foo::class)
->arg('$bar', ref('bar'))
->public()
('bar', Foo::class)
->call('setFoo')
;
};

View File

@ -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

View File

@ -0,0 +1,24 @@
<?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('../Prototype/{OtherDir}')
->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);
};

View File

@ -0,0 +1,122 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Bar\FooClass;
use Symfony\Component\DependencyInjection\Parameter;
require_once __DIR__.'/../includes/classes.php';
require_once __DIR__.'/../includes/foo.php';
return function (ContainerConfigurator $c) {
$p = $c->parameters();
$p->set('baz_class', 'BazClass');
$p->set('foo_class', FooClass::class)
->set('foo', 'bar');
$s = $c->services();
$s->set('foo')
->args(array('foo', ref('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, ref('service_container')))
->class(FooClass::class)
->tag('foo', array('foo' => 'foo'))
->tag('foo', array('bar' => 'bar', 'baz' => 'baz'))
->factory(array(FooClass::class, 'getInstance'))
->property('foo', 'bar')
->property('moo', ref('foo.baz'))
->property('qux', array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'))
->call('setBar', array(ref('bar')))
->call('initialize')
->configurator('sc_configure');
$s->set('foo.baz', '%baz_class%')
->factory(array('%baz_class%', 'getInstance'))
->configurator(array('%baz_class%', 'configureStatic1'));
$s->set('bar', FooClass::class)
->args(array('foo', ref('foo.baz'), new Parameter('foo_bar')))
->configurator(array(ref('foo.baz'), 'configure'));
$s->set('foo_bar', '%foo_class%')
->args(array(ref('deprecated_service')))
->share(false);
$s->alias('alias_for_foo', 'foo')->private()->public();
$s->alias('alias_for_alias', ref('alias_for_foo'));
$s->set('method_call1', 'Bar\FooClass')
->file(realpath(__DIR__.'/../includes/foo.php'))
->call('setBar', array(ref('foo')))
->call('setBar', array(ref('foo2')->nullOnInvalid()))
->call('setBar', array(ref('foo3')->ignoreOnInvalid()))
->call('setBar', array(ref('foobaz')->ignoreOnInvalid()))
->call('setBar', array(expr('service("foo").foo() ~ (container.hasParameter("foo") ? parameter("foo") : "default")')));
$s->set('foo_with_inline', 'Foo')
->call('setBar', array(ref('inlined')));
$s->set('inlined', 'Bar')
->property('pub', 'pub')
->call('setBaz', array(ref('baz')))
->private();
$s->set('baz', 'Baz')
->call('setFoo', array(ref('foo_with_inline')));
$s->set('request', 'Request')
->synthetic();
$s->set('configurator_service', 'ConfClass')
->private()
->call('setFoo', array(ref('baz')));
$s->set('configured_service', 'stdClass')
->configurator(array(ref('configurator_service'), 'configureStdClass'));
$s->set('configurator_service_simple', 'ConfClass')
->args(array('bar'))
->private();
$s->set('configured_service_simple', 'stdClass')
->configurator(array(ref('configurator_service_simple'), 'configureStdClass'));
$s->set('decorated', 'stdClass');
$s->set('decorator_service', 'stdClass')
->decorate('decorated');
$s->set('decorator_service_with_name', 'stdClass')
->decorate('decorated', 'decorated.pif-pouf');
$s->set('deprecated_service', 'stdClass')
->deprecate();
$s->set('new_factory', 'FactoryClass')
->property('foo', 'bar')
->private();
$s->set('factory_service', 'Bar')
->factory(array(ref('foo.baz'), 'getInstance'));
$s->set('new_factory_service', 'FooBarBaz')
->property('foo', 'bar')
->factory(array(ref('new_factory'), 'getInstance'));
$s->set('service_from_static_method', 'Bar\FooClass')
->factory(array('Bar\FooClass', 'getInstance'));
$s->set('factory_simple', 'SimpleFactoryClass')
->deprecate()
->args(array('foo'))
->private();
$s->set('factory_service_simple', 'Bar')
->factory(array(ref('factory_simple'), 'getInstance'));
$s->set('lazy_context', 'LazyContext')
->args(array(iterator(array('k1' => ref('foo.baz'), 'k2' => ref('service_container'))), iterator(array())));
$s->set('lazy_context_ignore_invalid_ref', 'LazyContext')
->args(array(iterator(array(ref('foo.baz'), ref('invalid')->ignoreOnInvalid())), iterator(array())));
};

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Dumper\YamlDumper;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\Config\FileLocator;
@ -35,4 +37,43 @@ class PhpFileLoaderTest extends TestCase
$this->assertEquals('foo', $container->getParameter('foo'), '->load() loads a PHP file resource');
}
public function testConfigServices()
{
$fixtures = realpath(__DIR__.'/../Fixtures');
$loader = new PhpFileLoader($container = new ContainerBuilder(), new FileLocator());
$loader->load($fixtures.'/config/services9.php');
$container->compile();
$dumper = new PhpDumper($container);
$this->assertStringEqualsFile($fixtures.'/php/services9_compiled.php', str_replace(str_replace('\\', '\\\\', $fixtures.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), '%path%', $dumper->dump()));
}
/**
* @dataProvider provideConfig
*/
public function testConfig($file)
{
$fixtures = realpath(__DIR__.'/../Fixtures');
$loader = new PhpFileLoader($container = new ContainerBuilder(), new FileLocator());
$loader->load($fixtures.'/config/'.$file.'.php');
$container->compile();
$dumper = new YamlDumper($container);
$this->assertStringEqualsFile($fixtures.'/config/'.$file.'.expected.yml', $dumper->dump());
}
public function provideConfig()
{
yield array('basic');
yield array('defaults');
yield array('instanceof');
yield array('prototype');
yield array('child');
if (\PHP_VERSION_ID >= 70000) {
yield array('php7');
}
}
}