feature #21553 [DI] Replace container injection by explicit service locators (chalasr)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DI] Replace container injection by explicit service locators

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20658
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/7458

This adds a new `ServiceLocatorArgument` (`!service_locator`) argument type which takes a list of services, meant to be used as a concrete service locator in order to avoid the remaining needs for injecting the container when it's only a matter of dependency lazy-loading.

Config:
```yaml
App\FooBar: [!service_locator { key1: '@service1', key2: '@service2' }]
```

```xml
<service class="App\FooBar" public="false">
    <argument type="service-locator">
        <argument type="service" key="key1" id="service1"/>
        <argument type="service" key="key2" id="service2"/>
     </argument>
</service>
```

```php
new ServiceLocatorArgument(array('key1' => new Reference('service1'), 'key2' => new Reference('service2'));
```

Usage:
```php
$locator->has('key1') // true
$locator->has('service1') // false, the defined key must be used
$locator->get('key1'); // service1 instance
$locator->get('service1'); // exception
$locator->has('not-specified') // false
$locator->get('not-specified'); // exception
```

We have some concrete use cases in the core where this would be useful (see e.g. SecurityBundle's FirewallMap), same in userland/3rd party code (see related RFC).

Commits
-------

e7935c0adb [DI] Replace container injection by explicit service locators
This commit is contained in:
Fabien Potencier 2017-02-13 15:17:30 +01:00
commit 320529e646
37 changed files with 474 additions and 9 deletions

View File

@ -16,6 +16,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\User
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@ -239,7 +240,7 @@ class SecurityExtension extends Extension
// load firewall map
$mapDef = $container->getDefinition('security.firewall.map');
$map = $authenticationProviders = array();
$map = $authenticationProviders = $contextRefs = array();
foreach ($firewalls as $name => $firewall) {
$configId = 'security.firewall.map.config.'.$name;
@ -253,8 +254,10 @@ class SecurityExtension extends Extension
->replaceArgument(2, new Reference($configId))
;
$contextRefs[$contextId] = new Reference($contextId);
$map[$contextId] = $matcher;
}
$mapDef->replaceArgument(0, new ServiceLocatorArgument($contextRefs));
$mapDef->replaceArgument(1, new IteratorArgument($map));
// add authentication providers to authentication manager

View File

@ -105,8 +105,8 @@
</service>
<service id="security.firewall.map" class="Symfony\Bundle\SecurityBundle\Security\FirewallMap" public="false">
<argument type="service" id="service_container" />
<argument />
<argument /> <!-- Firewall context locator -->
<argument /> <!-- Request matchers -->
</service>
<service id="security.firewall.context" class="Symfony\Bundle\SecurityBundle\Security\FirewallContext" abstract="true">

View File

@ -11,9 +11,9 @@
namespace Symfony\Bundle\SecurityBundle\Security;
use Psr\Container\ContainerInterface;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This is a lazy-loading firewall map implementation.
@ -116,9 +116,6 @@ class _FirewallMap
$this->contexts = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function getListeners(Request $request)
{
$context = $this->getFirewallContext($request);

View File

@ -0,0 +1,51 @@
<?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\Argument;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Represents a service locator able to lazy load a given range of services.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @experimental in version 3.3
*/
class ServiceLocatorArgument implements ArgumentInterface
{
private $values;
/**
* @param Reference[] $values An array of references indexed by identifier
*/
public function __construct(array $values)
{
$this->setValues($values);
}
public function getValues()
{
return $this->values;
}
public function setValues(array $values)
{
foreach ($values as $v) {
if (!$v instanceof Reference) {
throw new InvalidArgumentException('Values of a ServiceLocatorArgument must be Reference objects.');
}
}
$this->values = $values;
}
}

View File

@ -4,6 +4,7 @@ CHANGELOG
3.3.0
-----
* [EXPERIMENTAL] added "service-locator" argument for lazy loading a set of identified values and services
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
* added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info
* deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@ -1122,6 +1123,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
foreach ($value as $k => $v) {
$value[$k] = $this->resolveServices($v);
}
} elseif ($value instanceof ServiceLocatorArgument) {
$parameterBag = $this->getParameterBag();
$services = array();
foreach ($value->getValues() as $k => $v) {
$services[$k] = function () use ($v, $parameterBag) {
return $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v)));
};
}
$value = new ServiceLocator($services);
} elseif ($value instanceof IteratorArgument) {
$parameterBag = $this->getParameterBag();
$value = new RewindableGenerator(function () use ($value, $parameterBag) {

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -897,6 +898,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
$bagClass
/*{$this->docStar}
@ -1536,6 +1538,14 @@ EOF;
}
return sprintf('array(%s)', implode(', ', $code));
} elseif ($value instanceof ServiceLocatorArgument) {
$code = "\n";
foreach ($value->getValues() as $k => $v) {
$code .= sprintf(" %s => function () { return %s; },\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
}
$code .= ' ';
return sprintf('new ServiceLocator(array(%s))', $code);
} elseif ($value instanceof IteratorArgument) {
$countCode = array();
$countCode[] = 'function () {';

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
@ -291,6 +292,9 @@ class XmlDumper extends Dumper
if (is_array($value)) {
$element->setAttribute('type', 'collection');
$this->convertParameters($value, $type, $element, 'key');
} elseif ($value instanceof ServiceLocatorArgument) {
$element->setAttribute('type', 'service-locator');
$this->convertParameters($value->getValues(), $type, $element);
} elseif ($value instanceof IteratorArgument) {
$element->setAttribute('type', 'iterator');
$this->convertParameters($value->getValues(), $type, $element, 'key');

View File

@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Parameter;
@ -258,6 +259,8 @@ class YamlDumper extends Dumper
$tag = 'iterator';
} elseif ($value instanceof ClosureProxyArgument) {
$tag = 'closure_proxy';
} elseif ($value instanceof ServiceLocatorArgument) {
$tag = 'service_locator';
} else {
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_class($value)));
}

View File

@ -11,12 +11,12 @@
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Reference;
@ -498,6 +498,15 @@ class XmlFileLoader extends FileLoader
case 'iterator':
$arguments[$key] = new IteratorArgument($this->getArgumentsAsPhp($arg, $name, false));
break;
case 'service-locator':
$values = $this->getArgumentsAsPhp($arg, $name, false);
foreach ($values as $v) {
if (!$v instanceof Reference) {
throw new InvalidArgumentException('"service-locator" argument values must be services.');
}
}
$arguments[$key] = new ServiceLocatorArgument($values);
break;
case 'string':
$arguments[$key] = $arg->nodeValue;
break;

View File

@ -14,13 +14,13 @@ namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Tag\TaggedValue;
@ -616,6 +616,19 @@ class YamlFileLoader extends FileLoader
return new IteratorArgument(array_map(array($this, 'resolveServices'), $argument));
}
if ('service_locator' === $value->getTag()) {
if (!is_array($argument)) {
throw new InvalidArgumentException('"!service_locator" tag only accepts mappings.');
}
foreach ($argument as $v) {
if (!is_string($v) || 0 !== strpos($v[0], '@') || 0 === strpos($v[0], '@@')) {
throw new InvalidArgumentException('"!service_locator" tagged values must be {key: @service} mappings.');
}
}
return new ServiceLocatorArgument(array_map(array($this, 'resolveServices'), $argument));
}
if ('closure_proxy' === $value->getTag()) {
if (!is_array($argument) || array(0, 1) !== array_keys($argument) || !is_string($argument[0]) || !is_string($argument[1]) || 0 !== strpos($argument[0], '@') || 0 === strpos($argument[0], '@@')) {
throw new InvalidArgumentException('"!closure_proxy" tagged values must be arrays of [@service, method].');

View File

@ -246,6 +246,7 @@
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="iterator" />
<xsd:enumeration value="service-locator" />
<xsd:enumeration value="closure-proxy" />
</xsd:restriction>
</xsd:simpleType>

View File

@ -0,0 +1,71 @@
<?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;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @experimental in version 3.3
*/
class ServiceLocator implements PsrContainerInterface
{
private $factories;
private $values = array();
/**
* @param callable[] $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has($id)
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
if (!isset($this->factories[$id])) {
throw new ServiceNotFoundException($id, null, null, array_keys($this->factories));
}
if (true === $factory = $this->factories[$id]) {
throw new ServiceCircularReferenceException($id, array($id, $id));
}
if (false !== $factory) {
$this->factories[$id] = true;
$this->values[$id] = $factory();
$this->factories[$id] = false;
}
return $this->values[$id];
}
public function __invoke($id)
{
return isset($this->factories[$id]) ? $this->get($id) : null;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -33,6 +34,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
use Symfony\Component\ExpressionLanguage\Expression;
@ -437,6 +439,24 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, $i);
}
public function testCreateServiceWithServiceLocatorArgument()
{
$builder = new ContainerBuilder();
$builder->register('bar', 'stdClass');
$builder
->register('lazy_context', 'LazyContext')
->setArguments(array(new ServiceLocatorArgument(array('bar' => new Reference('bar'), 'invalid' => new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))))
;
$lazyContext = $builder->get('lazy_context');
$locator = $lazyContext->lazyValues;
$this->assertInstanceOf(ServiceLocator::class, $locator);
$this->assertInstanceOf('stdClass', $locator->get('bar'));
$this->assertNull($locator->get('invalid'));
$this->assertSame($locator->get('bar'), $locator('bar'), '->get() should be used when invoking ServiceLocator');
}
/**
* @expectedException \RuntimeException
*/

View File

@ -17,11 +17,13 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\ExpressionLanguage\Expression;
@ -503,6 +505,35 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
$dumper->dump();
}
public function testServiceLocatorArgumentProvideServiceLocator()
{
require_once self::$fixturesPath.'/includes/classes.php';
$container = new ContainerBuilder();
$container->register('lazy_referenced', 'stdClass');
$container
->register('lazy_context', 'LazyContext')
->setArguments(array(new ServiceLocatorArgument(array('lazy1' => new Reference('lazy_referenced'), 'lazy2' => new Reference('lazy_referenced'), 'container' => new Reference('service_container')))))
;
$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator'));
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_argument.php', $dump);
require_once self::$fixturesPath.'/php/services_locator_argument.php';
$container = new \Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator();
$lazyContext = $container->get('lazy_context');
$this->assertInstanceOf(ServiceLocator::class, $lazyContext->lazyValues);
$this->assertSame($container, $lazyContext->lazyValues->get('container'));
$this->assertInstanceOf('stdClass', $lazy1 = $lazyContext->lazyValues->get('lazy1'));
$this->assertInstanceOf('stdClass', $lazy2 = $lazyContext->lazyValues->get('lazy2'));
$this->assertSame($lazy1, $lazy2);
$this->assertFalse($lazyContext->lazyValues->has('lazy_referenced'), '->has() returns false for the original service id, only the key can be used');
}
public function testLazyArgumentProvideGenerator()
{
require_once self::$fixturesPath.'/includes/classes.php';

View File

@ -7,6 +7,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**

View File

@ -0,0 +1,84 @@
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator.
*
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator extends Container
{
private $parameters;
private $targetDirs = array();
/**
* Constructor.
*/
public function __construct()
{
$this->services = array();
$this->methodMap = array(
'lazy_context' => 'getLazyContextService',
'lazy_referenced' => 'getLazyReferencedService',
);
$this->aliases = array();
}
/**
* {@inheritdoc}
*/
public function compile()
{
throw new LogicException('You cannot compile a dumped frozen container.');
}
/**
* {@inheritdoc}
*/
public function isFrozen()
{
return true;
}
/**
* Gets the 'lazy_context' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \LazyContext A LazyContext instance
*/
protected function getLazyContextService()
{
return $this->services['lazy_context'] = new \LazyContext(new ServiceLocator(array(
'lazy1' => function () { return ${($_ = isset($this->services['lazy_referenced']) ? $this->services['lazy_referenced'] : $this->get('lazy_referenced')) && false ?: '_'}; },
'lazy2' => function () { return ${($_ = isset($this->services['lazy_referenced']) ? $this->services['lazy_referenced'] : $this->get('lazy_referenced')) && false ?: '_'}; },
'container' => function () { return $this; },
)));
}
/**
* Gets the 'lazy_referenced' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \stdClass A stdClass instance
*/
protected function getLazyReferencedService()
{
return $this->services['lazy_referenced'] = new \stdClass();
}
}

View File

@ -0,0 +1,25 @@
<?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">
<parameters>
<parameter key="baz_class">BazClass</parameter>
<parameter key="foo">bar</parameter>
</parameters>
<services>
<service id="foo.baz" class="%baz_class%">
<factory class="%baz_class%" method="getInstance"/>
<configurator class="%baz_class%" method="configureStatic1"/>
</service>
<service id="lazy_context" class="LazyContext">
<argument type="service-locator">
<argument key="foo_baz" type="service" id="foo.baz"/>
<argument key="container" type="service" id="service_container"/>
</argument>
</service>
<service id="lazy_context_ignore_invalid_ref" class="LazyContext">
<argument type="service-locator">
<argument key="foo_baz" type="service" id="foo.baz"/>
<argument key="invalid" type="service" id="invalid" on-invalid="ignore"/>
</argument>
</service>
</services>
</container>

View File

@ -0,0 +1,15 @@
parameters:
baz_class: BazClass
foo: bar
services:
foo.baz:
class: '%baz_class%'
factory: ['%baz_class%', getInstance]
configurator: ['%baz_class%', configureStatic1]
lazy_context:
class: LazyContext
arguments: [!service_locator {foo_baz: '@foo.baz', container: '@service_container'} ]
lazy_context_ignore_invalid_ref:
class: LazyContext
arguments: [!service_locator {foo_baz: '@foo.baz', invalid: '@?invalid'}]

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Loader;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@ -275,6 +276,16 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container')))), $lazyDefinition->getArguments(), '->load() parses lazy arguments');
}
public function testParsesServiceLocatorArgument()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_locator_argument.xml');
$this->assertEquals(array(new ServiceLocatorArgument(array('foo_baz' => new Reference('foo.baz'), 'container' => new Reference('service_container')))), $container->getDefinition('lazy_context')->getArguments(), '->load() parses service-locator arguments');
$this->assertEquals(array(new ServiceLocatorArgument(array('foo_baz' => new Reference('foo.baz'), 'invalid' => new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))), $container->getDefinition('lazy_context_ignore_invalid_ref')->getArguments(), '->load() parses service-locator arguments');
}
public function testParsesTags()
{
$container = new ContainerBuilder();

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Loader;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@ -23,6 +24,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
use Symfony\Component\ExpressionLanguage\Expression;
@ -352,6 +354,16 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container')))), $lazyDefinition->getArguments(), '->load() parses lazy arguments');
}
public function testParsesServiceLocatorArgument()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_locator_argument.yml');
$this->assertEquals(array(new ServiceLocatorArgument(array('foo_baz' => new Reference('foo.baz'), 'container' => new Reference('service_container')))), $container->getDefinition('lazy_context')->getArguments(), '->load() parses service-locator arguments');
$this->assertEquals(array(new ServiceLocatorArgument(array('foo_baz' => new Reference('foo.baz'), 'invalid' => new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))), $container->getDefinition('lazy_context_ignore_invalid_ref')->getArguments(), '->load() parses service-locator arguments');
}
public function testAutowire()
{
$container = new ContainerBuilder();

View File

@ -0,0 +1,78 @@
<?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\Tests;
use Symfony\Component\DependencyInjection\ServiceLocator;
class ServiceLocatorTest extends \PHPUnit_Framework_TestCase
{
public function testHas()
{
$locator = new ServiceLocator(array(
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
function () { return 'dummy'; },
));
$this->assertTrue($locator->has('foo'));
$this->assertTrue($locator->has('bar'));
$this->assertFalse($locator->has('dummy'));
}
public function testGet()
{
$locator = new ServiceLocator(array(
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('baz', $locator->get('bar'));
}
public function testGetDoesNotExecuteTheSameCallableTwice()
{
$i = 0;
$locator = new ServiceLocator(array('foo' => function () use (&$i) { $i++; return 'bar'; }));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame(1, $i);
}
/**
* @expectedException \Psr\Container\NotFoundExceptionInterface
* @expectedExceptionMessage You have requested a non-existent service "dummy"
*/
public function testGetThrowsOnUndefinedService()
{
$locator = new ServiceLocator(array(
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
));
$locator->get('dummy');
}
public function testInvoke()
{
$locator = new ServiceLocator(array(
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
));
$this->assertSame('bar', $locator('foo'));
$this->assertSame('baz', $locator('bar'));
$this->assertNull($locator('dummy'), '->__invoke() should return null on invalid service');
}
}