minor #30442 [OptionsResolver] Fix an error message to be more accurate (dimabory)

This PR was merged into the 3.4 branch.

Discussion
----------

[OptionsResolver] Fix an error message to be more accurate

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #30432
| License       | MIT
| Doc PR        |

See #30432 for more details:
> **Symfony version(s) affected**: 3.4, maybe other versions too (not tested)
>
> **Description**
> Error message when allowedTypes is an array contains `[]` but should not:
> `The option "testme" with value array is expected to be of type "string[]", but one of the elements is of type "integer[]".`
> It should be:
> `The option "testme" with value array is expected to be of type "string[]", but one of the elements is of type "integer".`
>
> **How to reproduce**
>
> ```
> $resolver = (new OptionsResolver())
>     ->setDefault('testme', [])
>     ->setAllowedTypes('testme', ['string[]'])
>     ->resolve(['testme' => ['test', 12]]);
> ```

In addition I changed an error message to be more
accurate if provided more than one incorrect value:
> [...] is expected to be of type "integer[][]", but is of type "integer|boolean|string".

Commits
-------

7fa2fc2 #30432 fix an error message
This commit is contained in:
Yonel Ceruto 2019-10-26 07:18:38 -04:00
commit fb70e0af8d
2 changed files with 38 additions and 27 deletions

View File

@ -734,7 +734,7 @@ class OptionsResolver implements Options
// Validate the type of the resolved option // Validate the type of the resolved option
if (isset($this->allowedTypes[$option])) { if (isset($this->allowedTypes[$option])) {
$valid = false; $valid = true;
$invalidTypes = []; $invalidTypes = [];
foreach ($this->allowedTypes[$option] as $type) { foreach ($this->allowedTypes[$option] as $type) {
@ -746,13 +746,22 @@ class OptionsResolver implements Options
} }
if (!$valid) { if (!$valid) {
$keys = array_keys($invalidTypes); $fmtActualValue = $this->formatValue($value);
$fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]);
$fmtProvidedTypes = implode('|', array_keys($invalidTypes));
if (1 === \count($keys) && '[]' === substr($keys[0], -2)) { $allowedContainsArrayType = \count(array_filter(
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), $keys[0])); $this->allowedTypes[$option],
function ($item) {
return '[]' === substr(isset(self::$typeAliases[$item]) ? self::$typeAliases[$item] : $item, -2);
}
)) > 0;
if (\is_array($value) && $allowedContainsArrayType) {
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
} }
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), implode('|', array_keys($invalidTypes)))); throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
} }
} }
@ -858,21 +867,14 @@ class OptionsResolver implements Options
{ {
$type = substr($type, 0, -2); $type = substr($type, 0, -2);
$suffix = '[]';
while (\strlen($suffix) <= $level * 2) {
$suffix .= '[]';
}
if ('[]' === substr($type, -2)) { if ('[]' === substr($type, -2)) {
$success = true; $success = true;
foreach ($value as $item) { foreach ($value as $item) {
if (!\is_array($item)) { if (!\is_array($item)) {
$invalidTypes[$this->formatTypeOf($item, null).$suffix] = true; $invalidTypes[$this->formatTypeOf($item, null)] = true;
return false; $success = false;
} } elseif (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
if (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
$success = false; $success = false;
} }
} }
@ -880,15 +882,17 @@ class OptionsResolver implements Options
return $success; return $success;
} }
$valid = true;
foreach ($value as $item) { foreach ($value as $item) {
if (!self::isValueValidType($type, $item)) { if (!self::isValueValidType($type, $item)) {
$invalidTypes[$this->formatTypeOf($item, $type).$suffix] = $value; $invalidTypes[$this->formatTypeOf($item, $type)] = $value;
return false; $valid = false;
} }
} }
return true; return $valid;
} }
/** /**

View File

@ -466,7 +466,7 @@ class OptionsResolverTest extends TestCase
public function testResolveFailsIfInvalidTypedArray() public function testResolveFailsIfInvalidTypedArray()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[]'); $this->resolver->setAllowedTypes('foo', 'int[]');
@ -486,9 +486,10 @@ class OptionsResolverTest extends TestCase
public function testResolveFailsIfTypedArrayContainsInvalidTypes() public function testResolveFailsIfTypedArrayContainsInvalidTypes()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass|array|DateTime".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[]'); $this->resolver->setAllowedTypes('foo', 'int[]');
$values = range(1, 5); $values = range(1, 5);
$values[] = new \stdClass(); $values[] = new \stdClass();
$values[] = []; $values[] = [];
@ -501,7 +502,7 @@ class OptionsResolverTest extends TestCase
public function testResolveFailsWithCorrectLevelsButWrongScalar() public function testResolveFailsWithCorrectLevelsButWrongScalar()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]'); $this->resolver->setAllowedTypes('foo', 'int[][]');
@ -537,6 +538,11 @@ class OptionsResolverTest extends TestCase
[42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'], [42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'],
[null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'], [null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'],
['bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'], ['bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'],
[['foo', 12], 'string[]', 'The option "option" with value array is expected to be of type "string[]", but one of the elements is of type "integer".'],
[123, ['string[]', 'string'], 'The option "option" with value 123 is expected to be of type "string[]" or "string", but is of type "integer".'],
[[null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "NULL".'],
[['string', null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "NULL".'],
[[\stdClass::class], ['string'], 'The option "option" with value array is expected to be of type "string", but is of type "array".'],
]; ];
} }
@ -585,6 +591,7 @@ class OptionsResolverTest extends TestCase
new \DateTime(), new \DateTime(),
], ],
]; ];
$result = $this->resolver->resolve($data); $result = $this->resolver->resolve($data);
$this->assertEquals($data, $result); $this->assertEquals($data, $result);
} }
@ -1535,7 +1542,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArraysException() public function testNestedArraysException()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'float[][][][]'); $this->resolver->setAllowedTypes('foo', 'float[][][][]');
@ -1553,7 +1560,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArrayException1() public function testNestedArrayException1()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean|string|array".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]'); $this->resolver->setAllowedTypes('foo', 'int[][]');
$this->resolver->resolve([ $this->resolver->resolve([
@ -1566,7 +1573,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArrayException2() public function testNestedArrayException2()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean|string|array".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]'); $this->resolver->setAllowedTypes('foo', 'int[][]');
$this->resolver->resolve([ $this->resolver->resolve([
@ -1579,7 +1586,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArrayException3() public function testNestedArrayException3()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string|integer".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[][][]'); $this->resolver->setAllowedTypes('foo', 'string[][][]');
$this->resolver->resolve([ $this->resolver->resolve([
@ -1592,7 +1599,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArrayException4() public function testNestedArrayException4()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[][][]'); $this->resolver->setAllowedTypes('foo', 'string[][][]');
$this->resolver->resolve([ $this->resolver->resolve([
@ -1606,7 +1613,7 @@ class OptionsResolverTest extends TestCase
public function testNestedArrayException5() public function testNestedArrayException5()
{ {
$this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException');
$this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]".'); $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array".');
$this->resolver->setDefined('foo'); $this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[]'); $this->resolver->setAllowedTypes('foo', 'string[]');
$this->resolver->resolve([ $this->resolver->resolve([