Skip to content

Commit cd7719f

Browse files
Merge branch '3.4' into 4.0
* 3.4: (22 commits) fix merge [Translation] Fix InvalidArgumentException when using untranslated plural forms from .po files Fixed exit code with non-integer throwable code [HttpFoundation] Support 0 bit netmask in IPv6 () [DI] Impossible to set an environment variable and then an array as container parameter [Process] remove false-positive BC breaking exception on Windows Tweaking class not found autowiring error [LDAP] added missing dots at the end of some exception messages. [TwigBridge] Add missing dev requirement for workflow fixed #25440 empty lines don't count for indent detection Set `width: auto` on WebProfiler toolbar's reset. [Lock] Fix incorrect phpdoc [Process] Dont rely on putenv(), it fails on ZTS PHP [HttpKernel] detect deprecations thrown by container initialization during tests [HttpKernel] Fix logging of post-terminate errors/exceptions [DI] Add context to service-not-found exceptions thrown by service locators [Debug] Fix catching fatal errors in case of nested error handlers [VarDumper] Fixed file links leave blank pages when ide is configured Fix hidden currency element with Bootstrap 3 theme ...
2 parents 5f5765e + 11ef58e commit cd7719f

10 files changed

+207
-29
lines changed

Compiler/AutowirePass.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\Config\Resource\ClassExistenceResource;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
@@ -314,7 +315,17 @@ private function set(string $type, string $id)
314315
private function createTypeNotFoundMessage(TypedReference $reference, $label)
315316
{
316317
if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
317-
$message = sprintf('has type "%s" but this class cannot be loaded.', $type);
318+
// either $type does not exist or a parent class does not exist
319+
try {
320+
$resource = new ClassExistenceResource($type, false);
321+
// isFresh() will explode ONLY if a parent class/trait does not exist
322+
$resource->isFresh(0);
323+
$parentMsg = false;
324+
} catch (\ReflectionException $e) {
325+
$parentMsg = $e->getMessage();
326+
}
327+
328+
$message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
318329
} else {
319330
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
320331
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $this->createTypeAlternatives($reference));

Compiler/RegisterServiceSubscribersPass.php

Lines 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
}

Compiler/ServiceLocatorTagPass.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,11 @@ protected function processValue($value, $isRoot = false)
7272
/**
7373
* @param ContainerBuilder $container
7474
* @param Reference[] $refMap
75+
* @param string|null $callerId
7576
*
7677
* @return Reference
7778
*/
78-
public static function register(ContainerBuilder $container, array $refMap)
79+
public static function register(ContainerBuilder $container, array $refMap, $callerId = null)
7980
{
8081
foreach ($refMap as $id => $ref) {
8182
if (!$ref instanceof Reference) {
@@ -94,6 +95,18 @@ public static function register(ContainerBuilder $container, array $refMap)
9495
$container->setDefinition($id, $locator);
9596
}
9697

98+
if (null !== $callerId) {
99+
$locatorId = $id;
100+
// Locators are shared when they hold the exact same list of factories;
101+
// to have them specialized per consumer service, we use a cloning factory
102+
// to derivate customized instances from the prototype one.
103+
$container->register($id .= '.'.$callerId, ServiceLocator::class)
104+
->setPublic(false)
105+
->setFactory(array(new Reference($locatorId), 'withContext'))
106+
->addArgument($callerId)
107+
->addArgument(new Reference('service_container'));
108+
}
109+
97110
return new Reference($id);
98111
}
99112
}

ContainerBuilder.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,11 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs
13131313
$format = '%%env(%s)%%';
13141314
}
13151315

1316+
$bag = $this->getParameterBag();
1317+
if (true === $format) {
1318+
$value = $bag->resolveValue($value);
1319+
}
1320+
13161321
if (is_array($value)) {
13171322
$result = array();
13181323
foreach ($value as $k => $v) {
@@ -1325,11 +1330,6 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs
13251330
if (!is_string($value)) {
13261331
return $value;
13271332
}
1328-
1329-
$bag = $this->getParameterBag();
1330-
if (true === $format) {
1331-
$value = $bag->resolveValue($value);
1332-
}
13331333
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
13341334

13351335
foreach ($envPlaceholders as $env => $placeholders) {

ServiceLocator.php

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
class ServiceLocator implements PsrContainerInterface
2323
{
2424
private $factories;
25+
private $loading = array();
26+
private $externalId;
27+
private $container;
2528

2629
/**
2730
* @param callable[] $factories
@@ -45,23 +48,98 @@ public function has($id)
4548
public function get($id)
4649
{
4750
if (!isset($this->factories[$id])) {
48-
throw new ServiceNotFoundException($id, null, null, array_keys($this->factories));
51+
throw new ServiceNotFoundException($id, end($this->loading) ?: null, null, array(), $this->createServiceNotFoundMessage($id));
4952
}
5053

51-
if (true === $factory = $this->factories[$id]) {
52-
throw new ServiceCircularReferenceException($id, array($id, $id));
54+
if (isset($this->loading[$id])) {
55+
$ids = array_values($this->loading);
56+
$ids = array_slice($this->loading, array_search($id, $ids));
57+
$ids[] = $id;
58+
59+
throw new ServiceCircularReferenceException($id, $ids);
5360
}
5461

55-
$this->factories[$id] = true;
62+
$this->loading[$id] = $id;
5663
try {
57-
return $factory();
64+
return $this->factories[$id]();
5865
} finally {
59-
$this->factories[$id] = $factory;
66+
unset($this->loading[$id]);
6067
}
6168
}
6269

6370
public function __invoke($id)
6471
{
6572
return isset($this->factories[$id]) ? $this->get($id) : null;
6673
}
74+
75+
/**
76+
* @internal
77+
*/
78+
public function withContext($externalId, Container $container)
79+
{
80+
$locator = clone $this;
81+
$locator->externalId = $externalId;
82+
$locator->container = $container;
83+
84+
return $locator;
85+
}
86+
87+
private function createServiceNotFoundMessage($id)
88+
{
89+
if ($this->loading) {
90+
return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives());
91+
}
92+
93+
$class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
94+
$class = isset($class[2]['object']) ? get_class($class[2]['object']) : null;
95+
$externalId = $this->externalId ?: $class;
96+
97+
$msg = sprintf('Service "%s" not found: ', $id);
98+
99+
if (!$this->container) {
100+
$class = null;
101+
} elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) {
102+
$msg .= 'even though it exists in the app\'s container, ';
103+
} else {
104+
try {
105+
$this->container->get($id);
106+
$class = null;
107+
} catch (ServiceNotFoundException $e) {
108+
if ($e->getAlternatives()) {
109+
$msg .= sprintf(' did you mean %s? Anyway, ', $this->formatAlternatives($e->getAlternatives(), 'or'));
110+
} else {
111+
$class = null;
112+
}
113+
}
114+
}
115+
if ($externalId) {
116+
$msg .= sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives());
117+
} else {
118+
$msg .= sprintf('the current service locator %s', $this->formatAlternatives());
119+
}
120+
121+
if (!$class) {
122+
// no-op
123+
} elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) {
124+
$msg .= sprintf(' Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
125+
} else {
126+
$msg .= 'Try using dependency injection instead.';
127+
}
128+
129+
return $msg;
130+
}
131+
132+
private function formatAlternatives(array $alternatives = null, $separator = 'and')
133+
{
134+
$format = '"%s"%s';
135+
if (null === $alternatives) {
136+
if (!$alternatives = array_keys($this->factories)) {
137+
return 'is empty...';
138+
}
139+
$format = sprintf('only knows about the %s service%s.', $format, 1 < count($alternatives) ? 's' : '');
140+
}
141+
$last = array_pop($alternatives);
142+
143+
return sprintf($format, $alternatives ? implode('", "', $alternatives) : $last, $alternatives ? sprintf(' %s "%s"', $separator, $last) : '');
144+
}
67145
}

Tests/Compiler/AutowirePassTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public function testDontTriggerAutowiring()
311311

312312
/**
313313
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
314-
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.
314+
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.
315315
*/
316316
public function testClassNotFoundThrowsException()
317317
{
@@ -328,7 +328,7 @@ public function testClassNotFoundThrowsException()
328328

329329
/**
330330
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
331-
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class cannot be loaded.
331+
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class is missing a parent class (Class Symfony\Bug\NotExistClass not found).
332332
*/
333333
public function testParentClassNotFoundThrowsException()
334334
{
@@ -679,7 +679,7 @@ public function testNotWireableCalls($method, $expectedMsg)
679679
public function provideNotWireableCalls()
680680
{
681681
return array(
682-
array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'),
682+
array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.'),
683683
array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'),
684684
array(null, 'Invalid service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
685685
);

Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines 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
/**

Tests/ContainerBuilderTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,28 @@ public function testResolveEnvValues()
616616
unset($_ENV['DUMMY_ENV_VAR'], $_SERVER['DUMMY_SERVER_VAR'], $_SERVER['HTTP_DUMMY_VAR']);
617617
}
618618

619+
public function testResolveEnvValuesWithArray()
620+
{
621+
$_ENV['ANOTHER_DUMMY_ENV_VAR'] = 'dummy';
622+
623+
$dummyArray = array('1' => 'one', '2' => 'two');
624+
625+
$container = new ContainerBuilder();
626+
$container->setParameter('dummy', '%env(ANOTHER_DUMMY_ENV_VAR)%');
627+
$container->setParameter('dummy2', $dummyArray);
628+
629+
$container->resolveEnvPlaceholders('%dummy%', true);
630+
$container->resolveEnvPlaceholders('%dummy2%', true);
631+
632+
$this->assertInternalType('array', $container->resolveEnvPlaceholders('%dummy2%', true));
633+
634+
foreach ($dummyArray as $key => $value) {
635+
$this->assertArrayHasKey($key, $container->resolveEnvPlaceholders('%dummy2%', true));
636+
}
637+
638+
unset($_ENV['ANOTHER_DUMMY_ENV_VAR']);
639+
}
640+
619641
public function testCompileWithResolveEnv()
620642
{
621643
putenv('DUMMY_ENV_VAR=du%%y');

Tests/Fixtures/php/services_subscriber.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public function getRemovedIds()
5858
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
5959
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,
6060
'service_locator.MtGsMEd' => true,
61+
'service_locator.MtGsMEd.foo_service' => true,
6162
);
6263
}
6364

@@ -78,14 +79,14 @@ protected function getTestServiceSubscriberService()
7879
*/
7980
protected function getFooServiceService()
8081
{
81-
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 (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
82+
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 (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
8283
return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
8384
}, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber {
8485
return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber());
8586
}, 'bar' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
8687
return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber());
8788
}, 'baz' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
8889
return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
89-
})));
90+
})), 'withContext']('foo_service', $this));
9091
}
9192
}

0 commit comments

Comments
 (0)