Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3afa88b

Browse filesBrowse files
[DI] Add context to service-not-found exceptions thrown by service locators
1 parent 22a6a7e commit 3afa88b
Copy full SHA for 3afa88b

File tree

Expand file treeCollapse file tree

7 files changed

+75
-21
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+75
-21
lines changed

‎src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected function processValue($value, $isRoot = false)
9494
throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId));
9595
}
9696

97-
$value->addTag('container.service_subscriber.locator', array('id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap)));
97+
$value->addTag('container.service_subscriber.locator', array('id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId)));
9898

9999
return parent::processValue($value);
100100
}

‎src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
+13-1Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ protected function processValue($value, $isRoot = false)
7272
/**
7373
* @param ContainerBuilder $container
7474
* @param Reference[] $refMap
75+
* @param string|null $callerId
7576
*
7677
* @return Reference
78+
*
79+
* @final since version 3.4
7780
*/
78-
public static function register(ContainerBuilder $container, array $refMap)
81+
public static function register(ContainerBuilder $container, array $refMap/*, string $callerId = null*/)
7982
{
8083
foreach ($refMap as $id => $ref) {
8184
if (!$ref instanceof Reference) {
@@ -94,6 +97,15 @@ public static function register(ContainerBuilder $container, array $refMap)
9497
$container->setDefinition($id, $locator);
9598
}
9699

100+
if (null !== $callerId = 2 < func_num_args() ? func_get_arg(2) : null) {
101+
$locatorId = $id;
102+
$container->register($id .= '.'.$callerId, ServiceLocator::class)
103+
->setPublic(false)
104+
->setFactory(array(new Reference($locatorId), 'withContext'))
105+
->addArgument($callerId)
106+
->addArgument(new Reference('service_container'));
107+
}
108+
97109
return new Reference($id);
98110
}
99111
}

‎src/Symfony/Component/DependencyInjection/ServiceLocator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/ServiceLocator.php
+53-6Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\DependencyInjection;
1313

1414
use Psr\Container\ContainerInterface as PsrContainerInterface;
15+
use Symfony\Component\DependencyInjection\Container;
1516
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
1617
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
18+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
1719

1820
/**
1921
* @author Robin Chalas <robin.chalas@gmail.com>
@@ -22,6 +24,9 @@
2224
class ServiceLocator implements PsrContainerInterface
2325
{
2426
private $factories;
27+
private $loading = array();
28+
private $externalId;
29+
private $container;
2530

2631
/**
2732
* @param callable[] $factories
@@ -31,6 +36,15 @@ public function __construct(array $factories)
3136
$this->factories = $factories;
3237
}
3338

39+
public function withContext($externalId, Container $container)
40+
{
41+
$locator = clone $this;
42+
$locator->externalId = $externalId;
43+
$locator->container = $container;
44+
45+
return $locator;
46+
}
47+
3448
/**
3549
* {@inheritdoc}
3650
*/
@@ -45,23 +59,56 @@ public function has($id)
4559
public function get($id)
4660
{
4761
if (!isset($this->factories[$id])) {
48-
throw new ServiceNotFoundException($id, null, null, array_keys($this->factories));
62+
throw new ServiceNotFoundException($id, end($this->loading) ?: null, null, array(), $this->createServiceNotFoundMessage($id));
4963
}
5064

51-
if (true === $factory = $this->factories[$id]) {
52-
throw new ServiceCircularReferenceException($id, array($id, $id));
65+
if (isset($this->loading[$id])) {
66+
$ids = array_values($this->loading);
67+
$ids = array_slice($this->loading, array_search($id, $ids));
68+
$ids[] = $id;
69+
70+
throw new ServiceCircularReferenceException($id, $ids);
5371
}
5472

55-
$this->factories[$id] = true;
73+
$this->loading[$id] = $id;
5674
try {
57-
return $factory();
75+
return $this->factories[$id]();
5876
} finally {
59-
$this->factories[$id] = $factory;
77+
unset($this->loading[$id]);
6078
}
6179
}
6280

6381
public function __invoke($id)
6482
{
6583
return isset($this->factories[$id]) ? $this->get($id) : null;
6684
}
85+
86+
private function createServiceNotFoundMessage($id)
87+
{
88+
if ($this->loading) {
89+
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', end($this->loading), $id);
90+
$msg .= sprintf(' This locator only knows about "%s"', implode('", "', array_keys($this->factories)));
91+
} else {
92+
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[2];
93+
$class = isset($class['object']) ? get_class($class['object']) : null;
94+
$externalId = $this->externalId ?: $class;
95+
96+
$msg = sprintf('Service "%s" not found: ', $id);
97+
98+
if ($this->container && ($this->container->has($id) || isset($this->container->getRemovedIds()[$id]))) {
99+
$msg .= 'even though it exists in the app\'s container, ';
100+
}
101+
if ($externalId) {
102+
$msg .= sprintf('"%s" only knows about pre-listed services "%s".', $externalId, implode('", "', array_keys($this->factories)));
103+
} else {
104+
$msg .= sprintf('the current locator only knows about "%s".', implode('", "', array_keys($this->factories)));
105+
}
106+
107+
if ($class && is_subclass_of($class, ServiceSubscriberInterface::class)) {
108+
$msg .= sprintf(' Did you forget to declare the depencency using "%s::getSubscribedServices()"?', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
109+
}
110+
}
111+
112+
return $msg;
113+
}
67114
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function testNoAttributes()
8585
'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, TestServiceSubscriber::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
8686
);
8787

88-
$this->assertEquals($expected, $locator->getArgument(0));
88+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
8989
}
9090

9191
public function testWithAttributes()
@@ -115,7 +115,7 @@ public function testWithAttributes()
115115
'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, TestServiceSubscriber::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
116116
);
117117

118-
$this->assertEquals($expected, $locator->getArgument(0));
118+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
119119
}
120120

121121
/**

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public function getRemovedIds()
4545
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
4646
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,
4747
'service_locator.jmktfsv' => true,
48+
'service_locator.jmktfsv.foo_service' => true,
4849
);
4950
}
5051

@@ -82,15 +83,15 @@ protected function getTestServiceSubscriberService()
8283
*/
8384
protected function getFooServiceService()
8485
{
85-
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function () {
86+
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(call_user_func(array(new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function () {
8687
$f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'});
8788
}, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function () {
8889
$f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'});
8990
}, 'bar' => function () {
9091
$f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'});
9192
}, 'baz' => function () {
9293
$f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'});
93-
})));
94+
})), 'withContext'), 'foo_service', $this));
9495
}
9596

9697
/**

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php
+2-8Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function testGetDoesNotMemoize()
5959

6060
/**
6161
* @expectedException \Psr\Container\NotFoundExceptionInterface
62-
* @expectedExceptionMessage You have requested a non-existent service "dummy". Did you mean one of these: "foo", "bar"?
62+
* @expectedExceptionMessage Service "dummy" not found: "Symfony\Component\DependencyInjection\Tests\ServiceLocatorTest" only knows about pre-listed services "foo", "bar".
6363
*/
6464
public function testGetThrowsOnUndefinedService()
6565
{
@@ -68,13 +68,7 @@ public function testGetThrowsOnUndefinedService()
6868
'bar' => function () { return 'baz'; },
6969
));
7070

71-
try {
72-
$locator->get('dummy');
73-
} catch (ServiceNotFoundException $e) {
74-
$this->assertSame(array('foo', 'bar'), $e->getAlternatives());
75-
76-
throw $e;
77-
}
71+
$locator->get('dummy');
7872
}
7973

8074
public function testInvoke()

‎src/Symfony/Component/HttpKernel/HttpKernel.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/HttpKernel.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
150150
$arguments = $event->getArguments();
151151

152152
// call controller
153-
$response = call_user_func_array($controller, $arguments);
153+
$response = \call_user_func_array($controller, $arguments);
154154

155155
// view
156156
if (!$response instanceof Response) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.