[DI] leverage Contracts\Service

This commit is contained in:
Nicolas Grekas 2018-08-16 10:54:12 +02:00
parent 25ca59d5e7
commit 87392ab30d
14 changed files with 48 additions and 97 deletions

View File

@ -50,7 +50,6 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Finder\Finder;
@ -102,6 +101,7 @@ use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* FrameworkExtension.

View File

@ -13,8 +13,8 @@ namespace Symfony\Bundle\FrameworkBundle\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* KernelTestCase is the base class for tests needing a Kernel.
@ -119,7 +119,7 @@ abstract class KernelTestCase extends TestCase
if (null !== static::$kernel) {
$container = static::$kernel->getContainer();
static::$kernel->shutdown();
if ($container instanceof ResettableContainerInterface) {
if ($container instanceof ResetInterface) {
$container->reset();
}
}

View File

@ -11,8 +11,9 @@
namespace Symfony\Component\Config\Resource;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface as LegacyServiceSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -157,7 +158,10 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
yield print_r(\call_user_func(array($class->name, 'getSubscribedEvents')), true);
}
if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
if (interface_exists(LegacyServiceSubscriberInterface::class, false) && $class->isSubclassOf(LegacyServiceSubscriberInterface::class)) {
yield LegacyServiceSubscriberInterface::class;
yield print_r(\call_user_func(array($class->name, 'getSubscribedServices')), true);
} elseif (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
yield ServiceSubscriberInterface::class;
yield print_r(\call_user_func(array($class->name, 'getSubscribedServices')), true);
}

View File

@ -10,6 +10,8 @@ CHANGELOG
* added `ServiceLocatorArgument` and `!service_locator` config tag for creating optimized service-locators
* added support for autoconfiguring bindings
* added `%env(key:...)%` processor to fetch a specific key from an array
* deprecated `ServiceSubscriberInterface`, use the same interface from the `Symfony\Contracts\Service` namespace instead
* deprecated `ResettableContainerInterface`, use `Symfony\Contracts\Service\ResetInterface` instead
4.1.0
-----

View File

@ -15,8 +15,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Compiler pass to register tagged services that require a service locator.

View File

@ -19,6 +19,8 @@ use Symfony\Contracts\Service\ResetInterface;
* not needed anymore.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @deprecated since Symfony 4.2, use "Symfony\Contracts\Service\ResetInterface" instead.
*/
interface ResettableContainerInterface extends ContainerInterface, ResetInterface
{

View File

@ -11,10 +11,14 @@
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\ServiceSubscriberInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
@ -22,51 +26,22 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
*/
class ServiceLocator implements PsrContainerInterface
{
private $factories;
private $loading = array();
use ServiceLocatorTrait {
get as private doGet;
}
private $externalId;
private $container;
/**
* @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, end($this->loading) ?: null, null, array(), $this->createServiceNotFoundMessage($id));
if (!$this->externalId) {
return $this->doGet($id);
}
if (isset($this->loading[$id])) {
$ids = array_values($this->loading);
$ids = \array_slice($this->loading, array_search($id, $ids));
$ids[] = $id;
throw new ServiceCircularReferenceException($id, $ids);
}
$this->loading[$id] = $id;
try {
return $this->factories[$id]();
return $this->doGet($id);
} catch (RuntimeException $e) {
if (!$this->externalId) {
throw $e;
}
$what = sprintf('service "%s" required by "%s"', $id, $this->externalId);
$message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage());
@ -79,8 +54,6 @@ class ServiceLocator implements PsrContainerInterface
$r->setValue($e, $message);
throw $e;
} finally {
unset($this->loading[$id]);
}
}
@ -101,14 +74,16 @@ class ServiceLocator implements PsrContainerInterface
return $locator;
}
private function createServiceNotFoundMessage($id)
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if ($this->loading) {
return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives());
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives());
return new ServiceNotFoundException($id, end($this->loading) ?: null, null, array(), $msg);
}
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$class = isset($class[2]['object']) ? \get_class($class[2]['object']) : null;
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null;
$externalId = $this->externalId ?: $class;
$msg = sprintf('Service "%s" not found: ', $id);
@ -143,7 +118,12 @@ class ServiceLocator implements PsrContainerInterface
$msg .= 'Try using dependency injection instead.';
}
return $msg;
return new ServiceNotFoundException($id, end($this->loading) ?: null, null, array(), $msg);
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new ServiceCircularReferenceException($id, $path);
}
private function formatAlternatives(array $alternatives = null, $separator = 'and')

View File

@ -11,40 +11,13 @@
namespace Symfony\Component\DependencyInjection;
use Symfony\Contracts\Service\ServiceSubscriberInterface as BaseServiceSubscriberInterface;
/**
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
* {@inheritdoc}
*
* The getSubscribedServices method returns an array of service types required by such instances,
* optionally keyed by the service names used internally. Service types that start with an interrogation
* mark "?" are optional, while the other ones are mandatory service dependencies.
*
* The injected service locators SHOULD NOT allow access to any other services not specified by the method.
*
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
* This interface does not dictate any injection method for these service locators, although constructor
* injection is recommended.
*
* @author Nicolas Grekas <p@tchwork.com>
* @deprecated since Symfony 4.2, use Symfony\Contracts\Service\ServiceSubscriberInterface instead.
*/
interface ServiceSubscriberInterface
interface ServiceSubscriberInterface extends BaseServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
* * array('logger' => 'Psr\Log\LoggerInterface') means the objects use the "logger" name
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
* * array('Psr\Log\LoggerInterface') is a shortcut for
* * array('Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface')
*
* otherwise:
*
* * array('logger' => '?Psr\Log\LoggerInterface') denotes an optional dependency
* * array('?Psr\Log\LoggerInterface') is a shortcut for
* * array('Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface')
*
* @return array The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
}

View File

@ -38,7 +38,7 @@ class RegisterServiceSubscribersPassTest extends TestCase
{
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Service "foo" must implement interface "Symfony\Component\DependencyInjection\ServiceSubscriberInterface".
* @expectedExceptionMessage Service "foo" must implement interface "Symfony\Contracts\Service\ServiceSubscriberInterface".
*/
public function testInvalidClass()
{

View File

@ -2,7 +2,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class TestServiceSubscriber implements ServiceSubscriberInterface
{

View File

@ -2,7 +2,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;
class TestServiceSubscriberParent implements ServiceSubscriberInterface

View File

@ -2,7 +2,7 @@
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
function sc_configure($instance)
{

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Tests\Service\ServiceLocatorTest as BaseServiceLocatorTest;
class ServiceLocatorTest extends BaseServiceLocatorTest
@ -74,7 +74,7 @@ class ServiceLocatorTest extends BaseServiceLocatorTest
}
}
class SomeServiceSubscriber implements ServiceSubscriberinterface
class SomeServiceSubscriber implements ServiceSubscriberInterface
{
public $container;

View File

@ -12,7 +12,6 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
@ -144,15 +143,6 @@ class TestSessionListenerTest extends TestCase
$this->filterResponse(new Request());
}
public function testDoesNotImplementServiceSubscriberInterface()
{
$this->assertTrue(interface_exists(ServiceSubscriberInterface::class));
$this->assertTrue(class_exists(SessionListener::class));
$this->assertTrue(class_exists(TestSessionListener::class));
$this->assertFalse(is_subclass_of(SessionListener::class, ServiceSubscriberInterface::class), 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford');
$this->assertFalse(is_subclass_of(TestSessionListener::class, ServiceSubscriberInterface::class, 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford'));
}
public function testDoesNotThrowIfRequestDoesNotHaveASession()
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();