[DI][Contracts] add and implement ServiceProviderInterface

This commit is contained in:
Mateusz Sip 2018-01-07 04:01:46 +01:00 committed by Nicolas Grekas
parent 708639f453
commit aaf5422cfb
14 changed files with 112 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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' => '?',
]);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ CHANGELOG
* added `HttpClient` namespace with contracts for implementing flexible HTTP clients
* added `EventDispatcher\EventDispatcherInterface`
* added `ServiceProviderInterface`
1.0.0
-----

View File

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

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