feature #25707 [DI] ServiceProviderInterface, implementation for ServiceLocator (kejwmen)
This PR was merged into the 4.3-dev branch.
Discussion
----------
[DI] ServiceProviderInterface, implementation for ServiceLocator
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #25686
| License | MIT
| Doc PR | applicable here?
Implements #25686
Not sure if it needs any additional documentation, @nicolas-grekas?
Commits
-------
aaf5422cfb
[DI][Contracts] add and implement ServiceProviderInterface
This commit is contained in:
commit
8551ec7ca9
@ -22,11 +22,13 @@ class ServiceLocator extends BaseServiceLocator
|
||||
{
|
||||
private $factory;
|
||||
private $serviceMap;
|
||||
private $serviceTypes;
|
||||
|
||||
public function __construct(\Closure $factory, array $serviceMap)
|
||||
public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->serviceMap = $serviceMap;
|
||||
$this->serviceTypes = $serviceTypes;
|
||||
parent::__construct($serviceMap);
|
||||
}
|
||||
|
||||
@ -37,4 +39,12 @@ class ServiceLocator extends BaseServiceLocator
|
||||
{
|
||||
return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProvidedServices(): array
|
||||
{
|
||||
return $this->serviceTypes ?? $this->serviceTypes = array_map(function () { return '?'; }, $this->serviceMap);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ CHANGELOG
|
||||
* added `ReverseContainer`: a container that turns services back to their ids
|
||||
* added ability to define an index for a tagged collection
|
||||
* added ability to define an index for services in an injected service locator argument
|
||||
* made `ServiceLocator` implement `ServiceProviderInterface`
|
||||
* deprecated support for non-string default env() parameters
|
||||
|
||||
4.2.0
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Contracts\Service\ServiceProviderInterface;
|
||||
|
||||
/**
|
||||
* Compiler pass to inject their service locator to service subscribers.
|
||||
@ -26,7 +27,7 @@ class ResolveServiceSubscribersPass extends AbstractRecursivePass
|
||||
|
||||
protected function processValue($value, $isRoot = false)
|
||||
{
|
||||
if ($value instanceof Reference && $this->serviceLocator && ContainerInterface::class === (string) $value) {
|
||||
if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) {
|
||||
return new Reference($this->serviceLocator);
|
||||
}
|
||||
|
||||
|
@ -1234,13 +1234,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
return $count;
|
||||
});
|
||||
} elseif ($value instanceof ServiceLocatorArgument) {
|
||||
$refs = [];
|
||||
$refs = $types = [];
|
||||
foreach ($value->getValues() as $k => $v) {
|
||||
if ($v) {
|
||||
$refs[$k] = [$v];
|
||||
$types[$k] = $v instanceof TypedReference ? $v->getType() : '?';
|
||||
}
|
||||
}
|
||||
$value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs);
|
||||
$value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types);
|
||||
} elseif ($value instanceof Reference) {
|
||||
$value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument);
|
||||
} elseif ($value instanceof Definition) {
|
||||
|
@ -1546,6 +1546,7 @@ EOF;
|
||||
|
||||
if ($value instanceof ServiceLocatorArgument) {
|
||||
$serviceMap = '';
|
||||
$serviceTypes = '';
|
||||
foreach ($value->getValues() as $k => $v) {
|
||||
if (!$v) {
|
||||
continue;
|
||||
@ -1559,11 +1560,12 @@ EOF;
|
||||
$this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id).($load ? '.php' : '') : null),
|
||||
$this->export($load)
|
||||
);
|
||||
$serviceTypes .= sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?'));
|
||||
$this->locatedIds[$id] = true;
|
||||
}
|
||||
$this->addGetService = true;
|
||||
|
||||
return sprintf('new \%s($this->getService, [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '');
|
||||
return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : '');
|
||||
}
|
||||
} finally {
|
||||
list($this->definitionVariables, $this->referenceVariables) = $scope;
|
||||
|
@ -12,19 +12,19 @@
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface as PsrContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Symfony\Contracts\Service\ServiceLocatorTrait;
|
||||
use Symfony\Contracts\Service\ServiceProviderInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ServiceLocator implements PsrContainerInterface
|
||||
class ServiceLocator implements ServiceProviderInterface
|
||||
{
|
||||
use ServiceLocatorTrait {
|
||||
get as private doGet;
|
||||
|
@ -72,6 +72,8 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container
|
||||
{
|
||||
return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [
|
||||
'rot13' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor', 'getRot13EnvVarProcessorService', false],
|
||||
], [
|
||||
'rot13' => '?',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,12 @@ class Symfony_DI_PhpDumper_Service_Locator_Argument extends Container
|
||||
'foo3' => [false, 'foo3', 'getFoo3Service', false],
|
||||
'foo4' => ['privates', 'foo4', NULL, 'BOOM'],
|
||||
'foo5' => ['services', 'foo5', NULL, false],
|
||||
], [
|
||||
'foo1' => '?',
|
||||
'foo2' => '?',
|
||||
'foo3' => '?',
|
||||
'foo4' => '?',
|
||||
'foo5' => '?',
|
||||
]);
|
||||
|
||||
return $instance;
|
||||
|
@ -75,6 +75,11 @@ class ProjectServiceContainer extends Container
|
||||
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false],
|
||||
'bar' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false],
|
||||
'baz' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'getCustomDefinitionService', false],
|
||||
], [
|
||||
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition',
|
||||
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber',
|
||||
'bar' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition',
|
||||
'baz' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition',
|
||||
]))->withContext('foo_service', $this));
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,21 @@ class ServiceLocatorTest extends BaseServiceLocatorTest
|
||||
$this->assertSame('baz', $locator('bar'));
|
||||
$this->assertNull($locator('dummy'), '->__invoke() should return null on invalid service');
|
||||
}
|
||||
|
||||
public function testProvidesServicesInformation()
|
||||
{
|
||||
$locator = new ServiceLocator([
|
||||
'foo' => function () { return 'bar'; },
|
||||
'bar' => function (): string { return 'baz'; },
|
||||
'baz' => function (): ?string { return 'zaz'; },
|
||||
]);
|
||||
|
||||
$this->assertSame($locator->getProvidedServices(), [
|
||||
'foo' => '?',
|
||||
'bar' => 'string',
|
||||
'baz' => '?string',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class SomeServiceSubscriber implements ServiceSubscriberInterface
|
||||
|
@ -18,7 +18,7 @@
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"psr/container": "^1.0",
|
||||
"symfony/contracts": "^1.0"
|
||||
"symfony/contracts": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/yaml": "~3.4|~4.0",
|
||||
|
@ -6,6 +6,7 @@ CHANGELOG
|
||||
|
||||
* added `HttpClient` namespace with contracts for implementing flexible HTTP clients
|
||||
* added `EventDispatcher\EventDispatcherInterface`
|
||||
* added `ServiceProviderInterface`
|
||||
|
||||
1.0.0
|
||||
-----
|
||||
|
@ -15,7 +15,7 @@ use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* A trait to help implement PSR-11 service locators.
|
||||
* A trait to help implement ServiceProviderInterface.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
@ -24,6 +24,7 @@ trait ServiceLocatorTrait
|
||||
{
|
||||
private $factories;
|
||||
private $loading = [];
|
||||
private $providedTypes;
|
||||
|
||||
/**
|
||||
* @param callable[] $factories
|
||||
@ -66,6 +67,28 @@ trait ServiceLocatorTrait
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProvidedServices(): array
|
||||
{
|
||||
if (null === $this->providedTypes) {
|
||||
$this->providedTypes = [];
|
||||
|
||||
foreach ($this->factories as $name => $factory) {
|
||||
if (!\is_callable($factory)) {
|
||||
$this->providedTypes[$name] = '?';
|
||||
} else {
|
||||
$type = (new \ReflectionFunction($factory))->getReturnType();
|
||||
|
||||
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->providedTypes;
|
||||
}
|
||||
|
||||
private function createNotFoundException(string $id): NotFoundExceptionInterface
|
||||
{
|
||||
if (!$alternatives = array_keys($this->factories)) {
|
||||
|
36
src/Symfony/Contracts/Service/ServiceProviderInterface.php
Normal file
36
src/Symfony/Contracts/Service/ServiceProviderInterface.php
Normal 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\Contracts\Service;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Mateusz Sip <mateusz.sip@gmail.com>
|
||||
*/
|
||||
interface ServiceProviderInterface extends ContainerInterface
|
||||
{
|
||||
/**
|
||||
* Returns an associative array of service types keyed by the identifiers provided by the current container.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
|
||||
* * ['foo' => '?'] means the container provides service name "foo" of unspecified type
|
||||
* * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
|
||||
*
|
||||
* @return string[] The provided service types, keyed by service names
|
||||
*/
|
||||
public function getProvidedServices(): array;
|
||||
}
|
Reference in New Issue
Block a user