Skip to content

Commit d9374de

Browse files
committed
Merge branch '3.1'
* 3.1: added a comment about a workaround [Finder] no PHP warning on empty directory iteration [HttpKernel] Fixed the nullable support for php 7.1 and below fixed CS [Form] Fix typo in doc comment Fix version constraint [Config] Handle open_basedir restrictions in FileLocator Fixed bad merge [DoctrineBridge][PropertyInfo] Treat Doctrine decimal type as string [bugfix] [Console] Set `Input::$interactive` to `false` when command is executed with `--quiet` as verbosity level Use JSON_UNESCAPED_SLASHES for lint commands output Fixed collapsed ChoiceType options attributes [FrameworkBundle] Remove cache clearer default value in config Consider the umask setting when dumping a file. Fixed the nullable support for php 7.1 and below Make ReflectionExtractor compatible with ReflectionType changes in PHP 7.1
2 parents dc0b339 + 292f5ae commit d9374de

File tree

10 files changed

+190
-14
lines changed

10 files changed

+190
-14
lines changed

Controller/ArgumentResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function getArguments(Request $request, $controller)
7474
$representative = get_class($representative);
7575
}
7676

77-
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $representative, $metadata->getName()));
77+
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName()));
7878
}
7979

8080
return $arguments;

Controller/ArgumentResolver/DefaultValueResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ final class DefaultValueResolver implements ArgumentValueResolverInterface
2727
*/
2828
public function supports(Request $request, ArgumentMetadata $argument)
2929
{
30-
return $argument->hasDefaultValue();
30+
return $argument->hasDefaultValue() || $argument->isNullable();
3131
}
3232

3333
/**
3434
* {@inheritdoc}
3535
*/
3636
public function resolve(Request $request, ArgumentMetadata $argument)
3737
{
38-
yield $argument->getDefaultValue();
38+
yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null;
3939
}
4040
}

Controller/ControllerResolver.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve
2727
{
2828
private $logger;
2929

30+
/**
31+
* If the ...$arg functionality is available.
32+
*
33+
* Requires at least PHP 5.6.0 or HHVM 3.9.1
34+
*
35+
* @var bool
36+
*/
37+
private $supportsVariadic;
38+
3039
/**
3140
* Constructor.
3241
*
@@ -35,6 +44,8 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve
3544
public function __construct(LoggerInterface $logger = null)
3645
{
3746
$this->logger = $logger;
47+
48+
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
3849
}
3950

4051
/**
@@ -104,6 +115,12 @@ public function getArguments(Request $request, $controller)
104115
}
105116

106117
/**
118+
* @param Request $request
119+
* @param callable $controller
120+
* @param \ReflectionParameter[] $parameters
121+
*
122+
* @return array The arguments to use when calling the action
123+
*
107124
* @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead.
108125
*/
109126
protected function doGetArguments(Request $request, $controller, array $parameters)
@@ -114,7 +131,7 @@ protected function doGetArguments(Request $request, $controller, array $paramete
114131
$arguments = array();
115132
foreach ($parameters as $param) {
116133
if (array_key_exists($param->name, $attributes)) {
117-
if (PHP_VERSION_ID >= 50600 && $param->isVariadic() && is_array($attributes[$param->name])) {
134+
if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) {
118135
$arguments = array_merge($arguments, array_values($attributes[$param->name]));
119136
} else {
120137
$arguments[] = $attributes[$param->name];
@@ -123,6 +140,8 @@ protected function doGetArguments(Request $request, $controller, array $paramete
123140
$arguments[] = $request;
124141
} elseif ($param->isDefaultValueAvailable()) {
125142
$arguments[] = $param->getDefaultValue();
143+
} elseif ($param->allowsNull()) {
144+
$arguments[] = null;
126145
} else {
127146
if (is_array($controller)) {
128147
$repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);

ControllerMetadata/ArgumentMetadata.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,24 @@ class ArgumentMetadata
2323
private $isVariadic;
2424
private $hasDefaultValue;
2525
private $defaultValue;
26+
private $isNullable;
2627

2728
/**
2829
* @param string $name
2930
* @param string $type
3031
* @param bool $isVariadic
3132
* @param bool $hasDefaultValue
3233
* @param mixed $defaultValue
34+
* @param bool $isNullable
3335
*/
34-
public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue)
36+
public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false)
3537
{
3638
$this->name = $name;
3739
$this->type = $type;
3840
$this->isVariadic = $isVariadic;
3941
$this->hasDefaultValue = $hasDefaultValue;
4042
$this->defaultValue = $defaultValue;
43+
$this->isNullable = (bool) $isNullable;
4144
}
4245

4346
/**
@@ -84,6 +87,16 @@ public function hasDefaultValue()
8487
return $this->hasDefaultValue;
8588
}
8689

90+
/**
91+
* Returns whether the argument is nullable in PHP 7.1 or higher.
92+
*
93+
* @return bool
94+
*/
95+
public function isNullable()
96+
{
97+
return $this->isNullable;
98+
}
99+
87100
/**
88101
* Returns the default value of the argument.
89102
*

ControllerMetadata/ArgumentMetadataFactory.php

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@
1818
*/
1919
final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
2020
{
21+
/**
22+
* If the ...$arg functionality is available.
23+
*
24+
* Requires at least PHP 5.6.0 or HHVM 3.9.1
25+
*
26+
* @var bool
27+
*/
28+
private $supportsVariadic;
29+
30+
/**
31+
* If the reflection supports the getType() method to resolve types.
32+
*
33+
* Requires at least PHP 7.0.0 or HHVM 3.11.0
34+
*
35+
* @var bool
36+
*/
37+
private $supportsParameterType;
38+
39+
public function __construct()
40+
{
41+
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
42+
$this->supportsParameterType = method_exists('ReflectionParameter', 'getType');
43+
}
44+
2145
/**
2246
* {@inheritdoc}
2347
*/
@@ -34,7 +58,7 @@ public function createArgumentMetadata($controller)
3458
}
3559

3660
foreach ($reflection->getParameters() as $param) {
37-
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param));
61+
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $this->isNullable($param));
3862
}
3963

4064
return $arguments;
@@ -49,7 +73,7 @@ public function createArgumentMetadata($controller)
4973
*/
5074
private function isVariadic(\ReflectionParameter $parameter)
5175
{
52-
return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
76+
return $this->supportsVariadic && $parameter->isVariadic();
5377
}
5478

5579
/**
@@ -64,6 +88,23 @@ private function hasDefaultValue(\ReflectionParameter $parameter)
6488
return $parameter->isDefaultValueAvailable();
6589
}
6690

91+
/**
92+
* Returns if the argument is allowed to be null but is still mandatory.
93+
*
94+
* @param \ReflectionParameter $parameter
95+
*
96+
* @return bool
97+
*/
98+
private function isNullable(\ReflectionParameter $parameter)
99+
{
100+
if ($this->supportsParameterType) {
101+
return null !== ($type = $parameter->getType()) && $type->allowsNull();
102+
}
103+
104+
// fallback for supported php 5.x versions
105+
return $this->hasDefaultValue($parameter) && null === $this->getDefaultValue($parameter);
106+
}
107+
67108
/**
68109
* Returns a default value if available.
69110
*
@@ -85,7 +126,7 @@ private function getDefaultValue(\ReflectionParameter $parameter)
85126
*/
86127
private function getType(\ReflectionParameter $parameter)
87128
{
88-
if (PHP_VERSION_ID >= 70000) {
129+
if ($this->supportsParameterType) {
89130
return $parameter->hasType() ? (string) $parameter->getType() : null;
90131
}
91132

Tests/Controller/ArgumentResolverTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
2020
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
2121
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingRequest;
22+
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController;
2223
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController;
2324
use Symfony\Component\HttpFoundation\Request;
2425

@@ -202,6 +203,32 @@ public function testGetArgumentWithoutArray()
202203
$resolver->getArguments($request, $controller);
203204
}
204205

206+
/**
207+
* @requires PHP 7.1
208+
*/
209+
public function testGetNullableArguments()
210+
{
211+
$request = Request::create('/');
212+
$request->attributes->set('foo', 'foo');
213+
$request->attributes->set('bar', new \stdClass());
214+
$request->attributes->set('mandatory', 'mandatory');
215+
$controller = array(new NullableController(), 'action');
216+
217+
$this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), self::$resolver->getArguments($request, $controller));
218+
}
219+
220+
/**
221+
* @requires PHP 7.1
222+
*/
223+
public function testGetNullableArgumentsWithDefaults()
224+
{
225+
$request = Request::create('/');
226+
$request->attributes->set('mandatory', 'mandatory');
227+
$controller = array(new NullableController(), 'action');
228+
229+
$this->assertEquals(array(null, null, 'value', 'mandatory'), self::$resolver->getArguments($request, $controller));
230+
}
231+
205232
public function __invoke($foo, $bar = null)
206233
{
207234
}

Tests/Controller/ControllerResolverTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
16+
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController;
1617
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController;
1718
use Symfony\Component\HttpFoundation\Request;
1819

@@ -226,6 +227,34 @@ public function testCreateControllerCanReturnAnyCallable()
226227
$mock->getController($request);
227228
}
228229

230+
/**
231+
* @requires PHP 7.1
232+
*/
233+
public function testGetNullableArguments()
234+
{
235+
$resolver = new ControllerResolver();
236+
237+
$request = Request::create('/');
238+
$request->attributes->set('foo', 'foo');
239+
$request->attributes->set('bar', new \stdClass());
240+
$request->attributes->set('mandatory', 'mandatory');
241+
$controller = array(new NullableController(), 'action');
242+
$this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), $resolver->getArguments($request, $controller));
243+
}
244+
245+
/**
246+
* @requires PHP 7.1
247+
*/
248+
public function testGetNullableArgumentsWithDefaults()
249+
{
250+
$resolver = new ControllerResolver();
251+
252+
$request = Request::create('/');
253+
$request->attributes->set('mandatory', 'mandatory');
254+
$controller = array(new NullableController(), 'action');
255+
$this->assertEquals(array(null, null, 'value', 'mandatory'), $resolver->getArguments($request, $controller));
256+
}
257+
229258
protected function createControllerResolver(LoggerInterface $logger = null)
230259
{
231260
return new ControllerResolver($logger);

Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1616
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
1717
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController;
18+
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController;
1819
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController;
1920

2021
class ArgumentMetadataFactoryTest extends \PHPUnit_Framework_TestCase
2122
{
23+
/**
24+
* @var ArgumentMetadataFactory
25+
*/
2226
private $factory;
2327

2428
protected function setUp()
@@ -42,9 +46,9 @@ public function testSignature2()
4246
$arguments = $this->factory->createArgumentMetadata(array($this, 'signature2'));
4347

4448
$this->assertEquals(array(
45-
new ArgumentMetadata('foo', self::class, false, true, null),
46-
new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null),
47-
new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null),
49+
new ArgumentMetadata('foo', self::class, false, true, null, true),
50+
new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null, true),
51+
new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null, true),
4852
), $arguments);
4953
}
5054

@@ -74,7 +78,7 @@ public function testSignature5()
7478
$arguments = $this->factory->createArgumentMetadata(array($this, 'signature5'));
7579

7680
$this->assertEquals(array(
77-
new ArgumentMetadata('foo', 'array', false, true, null),
81+
new ArgumentMetadata('foo', 'array', false, true, null, true),
7882
new ArgumentMetadata('bar', null, false, false, null),
7983
), $arguments);
8084
}
@@ -106,6 +110,21 @@ public function testBasicTypesSignature()
106110
), $arguments);
107111
}
108112

113+
/**
114+
* @requires PHP 7.1
115+
*/
116+
public function testNullableTypesSignature()
117+
{
118+
$arguments = $this->factory->createArgumentMetadata(array(new NullableController(), 'action'));
119+
120+
$this->assertEquals(array(
121+
new ArgumentMetadata('foo', 'string', false, false, null, true),
122+
new ArgumentMetadata('bar', \stdClass::class, false, false, null, true),
123+
new ArgumentMetadata('baz', 'string', false, true, 'value', true),
124+
new ArgumentMetadata('mandatory', null, false, false, null),
125+
), $arguments);
126+
}
127+
109128
private function signature1(ArgumentMetadataFactoryTest $foo, array $bar, callable $baz)
110129
{
111130
}

Tests/ControllerMetadata/ArgumentMetadataTest.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@
1515

1616
class ArgumentMetadataTest extends \PHPUnit_Framework_TestCase
1717
{
18-
public function testDefaultValueAvailable()
18+
public function testWithBcLayerWithDefault()
1919
{
2020
$argument = new ArgumentMetadata('foo', 'string', false, true, 'default value');
2121

22+
$this->assertFalse($argument->isNullable());
23+
}
24+
25+
public function testDefaultValueAvailable()
26+
{
27+
$argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true);
28+
29+
$this->assertTrue($argument->isNullable());
2230
$this->assertTrue($argument->hasDefaultValue());
2331
$this->assertSame('default value', $argument->getDefaultValue());
2432
}
@@ -28,8 +36,9 @@ public function testDefaultValueAvailable()
2836
*/
2937
public function testDefaultValueUnavailable()
3038
{
31-
$argument = new ArgumentMetadata('foo', 'string', false, false, null);
39+
$argument = new ArgumentMetadata('foo', 'string', false, false, null, false);
3240

41+
$this->assertFalse($argument->isNullable());
3342
$this->assertFalse($argument->hasDefaultValue());
3443
$argument->getDefaultValue();
3544
}

0 commit comments

Comments
 (0)