feature #19584 [DependencyInjection] Improve ParameterNotFoundException when accessing a nested parameter (wouterj)

This PR was squashed before being merged into the 3.2-dev branch (closes #19584).

Discussion
----------

[DependencyInjection] Improve ParameterNotFoundException when accessing a nested parameter

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | n/a

A common problem under beginners is to think that the dot notation is used to access nested arrays saved in parameters (*common here means someone asks help for this problem at least once a week on IRC*). Adding a little extra detail to the exception message and a working alternative should help pointing these people in the right direction before spending time debugging this.

It's quite late in the night over here, so the wording of the exception message probably isn't great. I'm happy to accept better suggestions 😃

Commits
-------

df70f06 [DependencyInjection] Improve ParameterNotFoundException when accessing a nested parameter
This commit is contained in:
Fabien Potencier 2016-09-14 14:30:35 -07:00
commit 0e63f4796d
3 changed files with 45 additions and 29 deletions

View File

@ -22,20 +22,23 @@ class ParameterNotFoundException extends InvalidArgumentException
private $sourceId;
private $sourceKey;
private $alternatives;
private $nonNestedAlternative;
/**
* @param string $key The requested parameter key
* @param string $sourceId The service id that references the non-existent parameter
* @param string $sourceKey The parameter key that references the non-existent parameter
* @param \Exception $previous The previous exception
* @param string[] $alternatives Some parameter name alternatives
* @param string $key The requested parameter key
* @param string $sourceId The service id that references the non-existent parameter
* @param string $sourceKey The parameter key that references the non-existent parameter
* @param \Exception $previous The previous exception
* @param string[] $alternatives Some parameter name alternatives
* @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters
*/
public function __construct($key, $sourceId = null, $sourceKey = null, \Exception $previous = null, array $alternatives = array())
public function __construct($key, $sourceId = null, $sourceKey = null, \Exception $previous = null, array $alternatives = array(), $nonNestedAlternative = null)
{
$this->key = $key;
$this->sourceId = $sourceId;
$this->sourceKey = $sourceKey;
$this->alternatives = $alternatives;
$this->nonNestedAlternative = $nonNestedAlternative;
parent::__construct('', 0, $previous);
@ -59,6 +62,8 @@ class ParameterNotFoundException extends InvalidArgumentException
$this->message .= ' Did you mean one of these: "';
}
$this->message .= implode('", "', $this->alternatives).'"?';
} elseif (null !== $this->nonNestedAlternative) {
$this->message .= ' You cannot access nested array items, do you want to inject "'.$this->nonNestedAlternative.'" instead?';
}
}

View File

@ -81,7 +81,23 @@ class ParameterBag implements ParameterBagInterface
}
}
throw new ParameterNotFoundException($name, null, null, null, $alternatives);
$nonNestedAlternative = null;
if (!count($alternatives) && false !== strpos($name, '.')) {
$namePartsLength = array_map('strlen', explode('.', $name));
$key = substr($name, 0, -1 * (1 + array_pop($namePartsLength)));
while (count($namePartsLength)) {
if ($this->has($key)) {
if (is_array($this->get($key))) {
$nonNestedAlternative = $key;
}
break;
}
$key = substr($key, 0, -1 * (1 + array_pop($namePartsLength)));
}
}
throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative);
}
return $this->parameters[$name];

View File

@ -71,37 +71,32 @@ class ParameterBagTest extends \PHPUnit_Framework_TestCase
}
}
public function testGetThrowParameterNotFoundException()
/**
* @dataProvider provideGetThrowParameterNotFoundExceptionData
*/
public function testGetThrowParameterNotFoundException($parameterKey, $exceptionMessage)
{
$bag = new ParameterBag(array(
'foo' => 'foo',
'bar' => 'bar',
'baz' => 'baz',
'fiz' => array('bar' => array('boo' => 12)),
));
try {
$bag->get('foo1');
$this->fail('->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException', $e, '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
$this->assertEquals('You have requested a non-existent parameter "foo1". Did you mean this: "foo"?', $e->getMessage(), '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException with some advices');
}
$this->setExpectedException('Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException', $exceptionMessage);
try {
$bag->get('bag');
$this->fail('->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException', $e, '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
$this->assertEquals('You have requested a non-existent parameter "bag". Did you mean one of these: "bar", "baz"?', $e->getMessage(), '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException with some advices');
}
$bag->get($parameterKey);
}
try {
$bag->get('');
$this->fail('->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException', $e, '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException if the key does not exist');
$this->assertEquals('You have requested a non-existent parameter "".', $e->getMessage(), '->get() throws an Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException with some advices');
}
public function provideGetThrowParameterNotFoundExceptionData()
{
return array(
array('foo1', 'You have requested a non-existent parameter "foo1". Did you mean this: "foo"?'),
array('bag', 'You have requested a non-existent parameter "bag". Did you mean one of these: "bar", "baz"?'),
array('', 'You have requested a non-existent parameter "".'),
array('fiz.bar.boo', 'You have requested a non-existent parameter "fiz.bar.boo". You cannot access nested array items, do you want to inject "fiz" instead?'),
);
}
public function testHas()