Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 25ca59d

Browse files
committed
feature #28206 [Contracts] Add traits+interfaces from the DI component (nicolas-grekas)
This PR was merged into the 4.2-dev branch. Discussion ---------- [Contracts] Add traits+interfaces from the DI component | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - * added `Service\ServiceSubscriberInterface` to declare the dependencies of a class that consumes a service locator * added `Service\ServiceSubscriberTrait` to implement `Service\ServiceSubscriberInterface` using methods' return types * added `Service\ServiceLocatorTrait` to help implement PSR-11 service locators Commits ------- 675abdc [Contracts] Add traits+interfaces from the DI component
2 parents 7c394e3 + 675abdc commit 25ca59d

11 files changed

+272
-88
lines changed

src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use Symfony\Component\DependencyInjection\Reference;
2323
use Symfony\Component\DependencyInjection\ServiceLocator;
2424
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
25-
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
2625
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
2726
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1;
2827
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition2;
@@ -31,6 +30,7 @@
3130
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberChild;
3231
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberParent;
3332
use Symfony\Component\DependencyInjection\TypedReference;
33+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
3434

3535
require_once __DIR__.'/../Fixtures/includes/classes.php';
3636

src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberChild.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
44

5-
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
5+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
66

77
class TestServiceSubscriberChild extends TestServiceSubscriberParent
88
{

src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriberParent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
44

55
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
6-
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
6+
use Symfony\Contracts\Service\ServiceSubscriberTrait;
77

88
class TestServiceSubscriberParent implements ServiceSubscriberInterface
99
{

src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,52 +11,16 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Tests;
1313

14-
use PHPUnit\Framework\TestCase;
1514
use Symfony\Component\DependencyInjection\Container;
16-
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1715
use Symfony\Component\DependencyInjection\ServiceLocator;
1816
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
17+
use Symfony\Contracts\Tests\Service\ServiceLocatorTest as BaseServiceLocatorTest;
1918

20-
class ServiceLocatorTest extends TestCase
19+
class ServiceLocatorTest extends BaseServiceLocatorTest
2120
{
22-
public function testHas()
21+
public function getServiceLocator(array $factories)
2322
{
24-
$locator = new ServiceLocator(array(
25-
'foo' => function () { return 'bar'; },
26-
'bar' => function () { return 'baz'; },
27-
function () { return 'dummy'; },
28-
));
29-
30-
$this->assertTrue($locator->has('foo'));
31-
$this->assertTrue($locator->has('bar'));
32-
$this->assertFalse($locator->has('dummy'));
33-
}
34-
35-
public function testGet()
36-
{
37-
$locator = new ServiceLocator(array(
38-
'foo' => function () { return 'bar'; },
39-
'bar' => function () { return 'baz'; },
40-
));
41-
42-
$this->assertSame('bar', $locator->get('foo'));
43-
$this->assertSame('baz', $locator->get('bar'));
44-
}
45-
46-
public function testGetDoesNotMemoize()
47-
{
48-
$i = 0;
49-
$locator = new ServiceLocator(array(
50-
'foo' => function () use (&$i) {
51-
++$i;
52-
53-
return 'bar';
54-
},
55-
));
56-
57-
$this->assertSame('bar', $locator->get('foo'));
58-
$this->assertSame('bar', $locator->get('foo'));
59-
$this->assertSame(2, $i);
23+
return new ServiceLocator($factories);
6024
}
6125

6226
/**
@@ -65,40 +29,21 @@ public function testGetDoesNotMemoize()
6529
*/
6630
public function testGetThrowsOnUndefinedService()
6731
{
68-
$locator = new ServiceLocator(array(
32+
$locator = $this->getServiceLocator(array(
6933
'foo' => function () { return 'bar'; },
7034
'bar' => function () { return 'baz'; },
7135
));
7236

7337
$locator->get('dummy');
7438
}
7539

76-
/**
77-
* @expectedException \Psr\Container\NotFoundExceptionInterface
78-
* @expectedExceptionMessage The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.
79-
*/
80-
public function testThrowsOnUndefinedInternalService()
81-
{
82-
$locator = new ServiceLocator(array(
83-
'foo' => function () use (&$locator) { return $locator->get('bar'); },
84-
));
85-
86-
$locator->get('foo');
87-
}
88-
8940
/**
9041
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
9142
* @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> baz -> bar".
9243
*/
9344
public function testThrowsOnCircularReference()
9445
{
95-
$locator = new ServiceLocator(array(
96-
'foo' => function () use (&$locator) { return $locator->get('bar'); },
97-
'bar' => function () use (&$locator) { return $locator->get('baz'); },
98-
'baz' => function () use (&$locator) { return $locator->get('bar'); },
99-
));
100-
101-
$locator->get('foo');
46+
parent::testThrowsOnCircularReference();
10247
}
10348

10449
/**
@@ -110,15 +55,15 @@ public function testThrowsInServiceSubscriber()
11055
$container = new Container();
11156
$container->set('foo', new \stdClass());
11257
$subscriber = new SomeServiceSubscriber();
113-
$subscriber->container = new ServiceLocator(array('bar' => function () {}));
58+
$subscriber->container = $this->getServiceLocator(array('bar' => function () {}));
11459
$subscriber->container = $subscriber->container->withContext('caller', $container);
11560

11661
$subscriber->getFoo();
11762
}
11863

11964
public function testInvoke()
12065
{
121-
$locator = new ServiceLocator(array(
66+
$locator = $this->getServiceLocator(array(
12267
'foo' => function () { return 'bar'; },
12368
'bar' => function () { return 'baz'; },
12469
));
@@ -127,20 +72,6 @@ public function testInvoke()
12772
$this->assertSame('baz', $locator('bar'));
12873
$this->assertNull($locator('dummy'), '->__invoke() should return null on invalid service');
12974
}
130-
131-
/**
132-
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
133-
* @expectedExceptionMessage Invalid service "foo" required by "external-id".
134-
*/
135-
public function testRuntimeException()
136-
{
137-
$locator = new ServiceLocator(array(
138-
'foo' => function () { throw new RuntimeException('Invalid service ".service_locator.abcdef".'); },
139-
));
140-
141-
$locator = $locator->withContext('external-id', new Container());
142-
$locator->get('foo');
143-
}
14475
}
14576

14677
class SomeServiceSubscriber implements ServiceSubscriberinterface

src/Symfony/Contracts/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ CHANGELOG
77
* added `Service\ResetInterface` to provide a way to reset an object to its initial state
88
* added `Translation\TranslatorInterface` and `Translation\TranslatorTrait`
99
* added `Cache` contract to extend PSR-6 with tag invalidation, callback-based computation and stampede protection
10+
* added `Service\ServiceSubscriberInterface` to declare the dependencies of a class that consumes a service locator
11+
* added `Service\ServiceSubscriberTrait` to implement `Service\ServiceSubscriberInterface` using methods' return types
12+
* added `Service\ServiceLocatorTrait` to help implement PSR-11 service locators
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Contracts\Service;
13+
14+
use Psr\Container\ContainerExceptionInterface;
15+
use Psr\Container\NotFoundExceptionInterface;
16+
17+
/**
18+
* A trait to help implement PSR-11 service locators.
19+
*
20+
* @author Robin Chalas <[email protected]>
21+
* @author Nicolas Grekas <[email protected]>
22+
*/
23+
trait ServiceLocatorTrait
24+
{
25+
private $factories;
26+
private $loading = array();
27+
28+
/**
29+
* @param callable[] $factories
30+
*/
31+
public function __construct(array $factories)
32+
{
33+
$this->factories = $factories;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function has($id)
40+
{
41+
return isset($this->factories[$id]);
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function get($id)
48+
{
49+
if (!isset($this->factories[$id])) {
50+
throw $this->createNotFoundException($id);
51+
}
52+
53+
if (isset($this->loading[$id])) {
54+
$ids = array_values($this->loading);
55+
$ids = \array_slice($this->loading, array_search($id, $ids));
56+
$ids[] = $id;
57+
58+
throw $this->createCircularReferenceException($id, $ids);
59+
}
60+
61+
$this->loading[$id] = $id;
62+
try {
63+
return $this->factories[$id]($this);
64+
} finally {
65+
unset($this->loading[$id]);
66+
}
67+
}
68+
69+
private function createNotFoundException(string $id): NotFoundExceptionInterface
70+
{
71+
if (!$alternatives = array_keys($this->factories)) {
72+
$message = 'is empty...';
73+
} else {
74+
$last = array_pop($alternatives);
75+
if ($alternatives) {
76+
$message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
77+
} else {
78+
$message = sprintf('only knows about the "%s" service.', $last);
79+
}
80+
}
81+
82+
if ($this->loading) {
83+
$message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
84+
} else {
85+
$message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
86+
}
87+
88+
return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
89+
};
90+
}
91+
92+
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
93+
{
94+
return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
95+
};
96+
}
97+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Contracts\Service;
13+
14+
/**
15+
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
16+
*
17+
* The getSubscribedServices method returns an array of service types required by such instances,
18+
* optionally keyed by the service names used internally. Service types that start with an interrogation
19+
* mark "?" are optional, while the other ones are mandatory service dependencies.
20+
*
21+
* The injected service locators SHOULD NOT allow access to any other services not specified by the method.
22+
*
23+
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
24+
* This interface does not dictate any injection method for these service locators, although constructor
25+
* injection is recommended.
26+
*
27+
* @author Nicolas Grekas <[email protected]>
28+
*/
29+
interface ServiceSubscriberInterface
30+
{
31+
/**
32+
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
33+
*
34+
* For mandatory dependencies:
35+
*
36+
* * array('logger' => 'Psr\Log\LoggerInterface') means the objects use the "logger" name
37+
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
38+
* * array('loggers' => 'Psr\Log\LoggerInterface[]') means the objects use the "loggers" name
39+
* internally to fetch an iterable of Psr\Log\LoggerInterface instances.
40+
* * array('Psr\Log\LoggerInterface') is a shortcut for
41+
* * array('Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface')
42+
*
43+
* otherwise:
44+
*
45+
* * array('logger' => '?Psr\Log\LoggerInterface') denotes an optional dependency
46+
* * array('loggers' => '?Psr\Log\LoggerInterface[]') denotes an optional iterable dependency
47+
* * array('?Psr\Log\LoggerInterface') is a shortcut for
48+
* * array('Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface')
49+
*
50+
* @return array The required service types, optionally keyed by service names
51+
*/
52+
public static function getSubscribedServices();
53+
}

src/Symfony/Component/DependencyInjection/ServiceSubscriberTrait.php renamed to src/Symfony/Contracts/Service/ServiceSubscriberTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\Component\DependencyInjection;
12+
namespace Symfony\Contracts\Service;
1313

1414
use Psr\Container\ContainerInterface;
1515

0 commit comments

Comments
 (0)