Merge branch '4.0' into 4.1

* 4.0:
  [Console] fix CS
  [OptionResolver] resolve arrays
  [TwigBridge] Fix missing path and separators in loader paths list on debug:twig output
  [PropertyInfo] Fix dock block lookup fallback loop
  [HttpFoundation] don't encode cookie name for BC
  improve deprecation messages
  minor #27858 [Console] changed warning verbosity; fixes typo (adrian-enspired)
  AppBundle->App.
  [DI] Fix dumping ignore-on-uninitialized references to synthetic services
This commit is contained in:
Nicolas Grekas 2018-07-07 18:00:36 +02:00
commit 88f704684d
15 changed files with 476 additions and 77 deletions

View File

@ -108,19 +108,27 @@ EOF
} }
$rows = array(); $rows = array();
$firstNamespace = true;
$prevHasSeparator = false;
foreach ($this->getLoaderPaths() as $namespace => $paths) { foreach ($this->getLoaderPaths() as $namespace => $paths) {
if (count($paths) > 1) { if (!$firstNamespace && !$prevHasSeparator && count($paths) > 1) {
$rows[] = array('', ''); $rows[] = array('', '');
} }
$firstNamespace = false;
foreach ($paths as $path) { foreach ($paths as $path) {
$rows[] = array($namespace, '- '.$path); $rows[] = array($namespace, $path.DIRECTORY_SEPARATOR);
$namespace = ''; $namespace = '';
} }
if (count($paths) > 1) { if (count($paths) > 1) {
$rows[] = array('', ''); $rows[] = array('', '');
$prevHasSeparator = true;
} else {
$prevHasSeparator = false;
} }
} }
array_pop($rows); if ($prevHasSeparator) {
array_pop($rows);
}
$io->section('Loader Paths'); $io->section('Loader Paths');
$io->table(array('Namespace', 'Paths'), $rows); $io->table(array('Namespace', 'Paths'), $rows);

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Command\DebugCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Twig\Loader\FilesystemLoader;
use Twig\Environment;
class DebugCommandTest extends TestCase
{
public function testDebugCommand()
{
$tester = $this->createCommandTester();
$ret = $tester->execute(array(), array('decorated' => false));
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertContains('Functions', trim($tester->getDisplay()));
}
public function testLineSeparatorInLoaderPaths()
{
// these paths aren't realistic,
// they're configured to force the line separator
$tester = $this->createCommandTester(array(
'Acme' => array('extractor', 'extractor'),
'!Acme' => array('extractor', 'extractor'),
FilesystemLoader::MAIN_NAMESPACE => array('extractor', 'extractor'),
));
$ret = $tester->execute(array(), array('decorated' => false));
$ds = DIRECTORY_SEPARATOR;
$loaderPaths = <<<TXT
Loader Paths
------------
----------- ------------
Namespace Paths
----------- ------------
@Acme extractor$ds
extractor$ds
@!Acme extractor$ds
extractor$ds
(None) extractor$ds
extractor$ds
----------- ------------
TXT;
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertContains($loaderPaths, trim($tester->getDisplay(true)));
}
private function createCommandTester(array $paths = array())
{
$filesystemLoader = new FilesystemLoader(array(), dirname(__DIR__).'/Fixtures');
foreach ($paths as $namespace => $relDirs) {
foreach ($relDirs as $relDir) {
$filesystemLoader->addPath($relDir, $namespace);
}
}
$command = new DebugCommand(new Environment($filesystemLoader));
$application = new Application();
$application->add($command);
$command = $application->find('debug:twig');
return new CommandTester($command);
}
}

View File

@ -70,7 +70,7 @@ Suppose that you have the following security configuration in your application:
security: security:
encoders: encoders:
Symfony\Component\Security\Core\User\User: plaintext Symfony\Component\Security\Core\User\User: plaintext
AppBundle\Entity\User: bcrypt App\Entity\User: bcrypt
</comment> </comment>
If you execute the command non-interactively, the first available configured If you execute the command non-interactively, the first available configured
@ -82,16 +82,16 @@ generated to encode the password:
Pass the full user class path as the second argument to encode passwords for Pass the full user class path as the second argument to encode passwords for
your own entities: your own entities:
<info>php %command.full_name% --no-interaction [password] AppBundle\Entity\User</info> <info>php %command.full_name% --no-interaction [password] App\Entity\User</info>
Executing the command interactively allows you to generate a random salt for Executing the command interactively allows you to generate a random salt for
encoding the password: encoding the password:
<info>php %command.full_name% [password] AppBundle\Entity\User</info> <info>php %command.full_name% [password] App\Entity\User</info>
In case your encoder doesn't require a salt, add the <comment>empty-salt</comment> option: In case your encoder doesn't require a salt, add the <comment>empty-salt</comment> option:
<info>php %command.full_name% --empty-salt [password] AppBundle\Entity\User</info> <info>php %command.full_name% --empty-salt [password] App\Entity\User</info>
EOF EOF
) )

View File

@ -363,8 +363,8 @@ class MainConfiguration implements ConfigurationInterface
->children() ->children()
->arrayNode('encoders') ->arrayNode('encoders')
->example(array( ->example(array(
'AppBundle\Entity\User1' => 'bcrypt', 'App\Entity\User1' => 'bcrypt',
'AppBundle\Entity\User2' => array( 'App\Entity\User2' => array(
'algorithm' => 'bcrypt', 'algorithm' => 'bcrypt',
'cost' => 13, 'cost' => 13,
), ),

View File

@ -220,7 +220,7 @@ class Command
if (function_exists('cli_set_process_title')) { if (function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) { if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) { if ('Darwin' === PHP_OS) {
$output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>'); $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else { } else {
cli_set_process_title($this->processTitle); cli_set_process_title($this->processTitle);
} }

View File

@ -1662,8 +1662,10 @@ EOF;
return '$this'; return '$this';
} }
if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) { if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) {
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { if ($definition->isSynthetic()) {
$code = sprintf('$this->get(\'%s\'%s)', $id, null !== $reference ? ', '.$reference->getInvalidBehavior() : '');
} elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
$code = 'null'; $code = 'null';
if (!$definition->isShared()) { if (!$definition->isShared()) {
return $code; return $code;

View File

@ -1399,6 +1399,21 @@ class ContainerBuilderTest extends TestCase
$this->assertSame('via-bindings', $container->get('foo')->class2->identifier); $this->assertSame('via-bindings', $container->get('foo')->class2->identifier);
} }
public function testUninitializedSyntheticReference()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setPublic(true)->setSynthetic(true);
$container->register('bar', 'stdClass')->setPublic(true)->setShared(false)
->setProperty('foo', new Reference('foo', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE));
$container->compile();
$this->assertEquals((object) array('foo' => null), $container->get('bar'));
$container->set('foo', (object) array(123));
$this->assertEquals((object) array('foo' => (object) array(123)), $container->get('bar'));
}
public function testIdCanBeAnObjectAsLongAsItCanBeCastToString() public function testIdCanBeAnObjectAsLongAsItCanBeCastToString()
{ {
$id = new Reference('another_service'); $id = new Reference('another_service');

View File

@ -953,6 +953,29 @@ class PhpDumperTest extends TestCase
$this->assertInstanceOf('stdClass', $container->get('bar')); $this->assertInstanceOf('stdClass', $container->get('bar'));
} }
public function testUninitializedSyntheticReference()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setPublic(true)->setSynthetic(true);
$container->register('bar', 'stdClass')->setPublic(true)->setShared(false)
->setProperty('foo', new Reference('foo', ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE));
$container->compile();
$dumper = new PhpDumper($container);
eval('?>'.$dumper->dump(array(
'class' => 'Symfony_DI_PhpDumper_Test_UninitializedSyntheticReference',
'inline_class_loader_parameter' => 'inline_requires',
)));
$container = new \Symfony_DI_PhpDumper_Test_UninitializedSyntheticReference();
$this->assertEquals((object) array('foo' => null), $container->get('bar'));
$container->set('foo', (object) array(123));
$this->assertEquals((object) array('foo' => (object) array(123)), $container->get('bar'));
}
/** /**
* This test checks the trigger of a deprecation note and should not be removed in major releases. * This test checks the trigger of a deprecation note and should not be removed in major releases.
* *

View File

@ -329,12 +329,17 @@ class Response
} }
// headers // headers
foreach ($this->headers->allPreserveCase() as $name => $values) { foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($values as $value) { foreach ($values as $value) {
header($name.': '.$value, false, $this->statusCode); header($name.': '.$value, false, $this->statusCode);
} }
} }
// cookies
foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
}
// status // status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

View File

@ -4,7 +4,7 @@ Array
[0] => Content-Type: text/plain; charset=utf-8 [0] => Content-Type: text/plain; charset=utf-8
[1] => Cache-Control: no-cache, private [1] => Cache-Control: no-cache, private
[2] => Date: Sat, 12 Nov 1955 20:04:00 GMT [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
[3] => Set-Cookie: %3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ [3] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
[4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ [4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
) )
shutdown shutdown

View File

@ -433,7 +433,7 @@ class OptionsResolver implements Options
)); ));
} }
$this->allowedValues[$option] = is_array($allowedValues) ? $allowedValues : array($allowedValues); $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : array($allowedValues);
// Make sure the option is processed // Make sure the option is processed
unset($this->resolved[$option]); unset($this->resolved[$option]);
@ -785,14 +785,13 @@ class OptionsResolver implements Options
} }
if (!$valid) { if (!$valid) {
throw new InvalidOptionsException(sprintf( $keys = array_keys($invalidTypes);
'The option "%s" with value %s is expected to be of type '.
'"%s", but is of type "%s".', if (1 === \count($keys) && '[]' === substr($keys[0], -2)) {
$option, 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->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, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), implode('|', array_keys($invalidTypes))));
));
} }
} }
@ -868,32 +867,10 @@ class OptionsResolver implements Options
return $value; return $value;
} }
/** private function verifyTypes(string $type, $value, array &$invalidTypes): bool
* @param string $type
* @param mixed $value
* @param array &$invalidTypes
*
* @return bool
*/
private function verifyTypes($type, $value, array &$invalidTypes)
{ {
if ('[]' === substr($type, -2) && is_array($value)) { if (\is_array($value) && '[]' === substr($type, -2)) {
$originalType = $type; return $this->verifyArrayType($type, $value, $invalidTypes);
$type = substr($type, 0, -2);
$invalidValues = array_filter( // Filter out valid values, keeping invalid values in the resulting array
$value,
function ($value) use ($type) {
return !self::isValueValidType($type, $value);
}
);
if (!$invalidValues) {
return true;
}
$invalidTypes[$this->formatTypeOf($value, $originalType)] = true;
return false;
} }
if (self::isValueValidType($type, $value)) { if (self::isValueValidType($type, $value)) {
@ -907,6 +884,43 @@ class OptionsResolver implements Options
return false; return false;
} }
private function verifyArrayType(string $type, array $value, array &$invalidTypes, int $level = 0): bool
{
$type = substr($type, 0, -2);
$suffix = '[]';
while (\strlen($suffix) <= $level * 2) {
$suffix .= '[]';
}
if ('[]' === substr($type, -2)) {
$success = true;
foreach ($value as $item) {
if (!\is_array($item)) {
$invalidTypes[$this->formatTypeOf($item, null).$suffix] = true;
return false;
}
if (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
$success = false;
}
}
return $success;
}
foreach ($value as $item) {
if (!self::isValueValidType($type, $item)) {
$invalidTypes[$this->formatTypeOf($item, $type).$suffix] = $value;
return false;
}
}
return true;
}
/** /**
* Returns whether a resolved option with the given name exists. * Returns whether a resolved option with the given name exists.
* *
@ -987,13 +1001,13 @@ class OptionsResolver implements Options
while ('[]' === substr($type, -2)) { while ('[]' === substr($type, -2)) {
$type = substr($type, 0, -2); $type = substr($type, 0, -2);
$value = array_shift($value); $value = array_shift($value);
if (!is_array($value)) { if (!\is_array($value)) {
break; break;
} }
$suffix .= '[]'; $suffix .= '[]';
} }
if (is_array($value)) { if (\is_array($value)) {
$subTypes = array(); $subTypes = array();
foreach ($value as $val) { foreach ($value as $val) {
$subTypes[$this->formatTypeOf($val, null)] = true; $subTypes[$this->formatTypeOf($val, null)] = true;
@ -1003,7 +1017,7 @@ class OptionsResolver implements Options
} }
} }
return (is_object($value) ? get_class($value) : gettype($value)).$suffix; return (\is_object($value) ? get_class($value) : gettype($value)).$suffix;
} }
/** /**
@ -1017,19 +1031,19 @@ class OptionsResolver implements Options
*/ */
private function formatValue($value): string private function formatValue($value): string
{ {
if (is_object($value)) { if (\is_object($value)) {
return get_class($value); return get_class($value);
} }
if (is_array($value)) { if (\is_array($value)) {
return 'array'; return 'array';
} }
if (is_string($value)) { if (\is_string($value)) {
return '"'.$value.'"'; return '"'.$value.'"';
} }
if (is_resource($value)) { if (\is_resource($value)) {
return 'resource'; return 'resource';
} }
@ -1065,8 +1079,21 @@ class OptionsResolver implements Options
return implode(', ', $values); return implode(', ', $values);
} }
private static function isValueValidType($type, $value) private static function isValueValidType(string $type, $value): bool
{ {
return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type; return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type;
} }
private function getInvalidValues(array $arrayValues, string $type): array
{
$invalidValues = array();
foreach ($arrayValues as $key => $value) {
if (!self::isValueValidType($type, $value)) {
$invalidValues[$key] = $value;
}
}
return $invalidValues;
}
} }

View File

@ -483,7 +483,7 @@ class OptionsResolverTest extends TestCase
/** /**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "DateTime[]". * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]".
*/ */
public function testResolveFailsIfInvalidTypedArray() public function testResolveFailsIfInvalidTypedArray()
{ {
@ -507,7 +507,7 @@ class OptionsResolverTest extends TestCase
/** /**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "integer|stdClass|array|DateTime[]". * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]".
*/ */
public function testResolveFailsIfTypedArrayContainsInvalidTypes() public function testResolveFailsIfTypedArrayContainsInvalidTypes()
{ {
@ -524,7 +524,7 @@ class OptionsResolverTest extends TestCase
/** /**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but is of type "double[][]". * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]".
*/ */
public function testResolveFailsWithCorrectLevelsButWrongScalar() public function testResolveFailsWithCorrectLevelsButWrongScalar()
{ {
@ -1586,4 +1586,151 @@ class OptionsResolverTest extends TestCase
count($this->resolver); count($this->resolver);
} }
public function testNestedArrays()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]');
$this->assertEquals(array(
'foo' => array(
array(
1, 2,
),
),
), $this->resolver->resolve(
array(
'foo' => array(
array(1, 2),
),
)
));
}
public function testNested2Arrays()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][][][]');
$this->assertEquals(array(
'foo' => array(
array(
array(
array(
1, 2,
),
),
),
),
), $this->resolver->resolve(
array(
'foo' => array(
array(
array(
array(1, 2),
),
),
),
)
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]".
*/
public function testNestedArraysException()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'float[][][][]');
$this->resolver->resolve(
array(
'foo' => array(
array(
array(
array(1, 2),
),
),
),
)
);
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
*/
public function testNestedArrayException1()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]');
$this->resolver->resolve(array(
'foo' => array(
array(1, true, 'str', array(2, 3)),
),
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
*/
public function testNestedArrayException2()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'int[][]');
$this->resolver->resolve(array(
'foo' => array(
array(true, 'str', array(2, 3)),
),
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]".
*/
public function testNestedArrayException3()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[][][]');
$this->resolver->resolve(array(
'foo' => array(
array('str', array(1, 2)),
),
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]".
*/
public function testNestedArrayException4()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[][][]');
$this->resolver->resolve(array(
'foo' => array(
array(
array('str'), array(1, 2), ),
),
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]".
*/
public function testNestedArrayException5()
{
$this->resolver->setDefined('foo');
$this->resolver->setAllowedTypes('foo', 'string[]');
$this->resolver->resolve(array(
'foo' => array(
array(
array('str'), array(1, 2), ),
),
));
}
} }

View File

@ -161,25 +161,21 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
$ucFirstProperty = ucfirst($property); $ucFirstProperty = ucfirst($property);
try { switch (true) {
switch (true) { case $docBlock = $this->getDocBlockFromProperty($class, $property):
case $docBlock = $this->getDocBlockFromProperty($class, $property): $data = array($docBlock, self::PROPERTY, null);
$data = array($docBlock, self::PROPERTY, null); break;
break;
case list($docBlock) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): case list($docBlock) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR):
$data = array($docBlock, self::ACCESSOR, null); $data = array($docBlock, self::ACCESSOR, null);
break; break;
case list($docBlock, $prefix) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): case list($docBlock, $prefix) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR):
$data = array($docBlock, self::MUTATOR, $prefix); $data = array($docBlock, self::MUTATOR, $prefix);
break; break;
default: default:
$data = array(null, null, null); $data = array(null, null, null);
}
} catch (\InvalidArgumentException $e) {
$data = array(null, null, null);
} }
return $this->docBlocks[$propertyHash] = $data; return $this->docBlocks[$propertyHash] = $data;
@ -194,7 +190,11 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
return null; return null;
} }
return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass())); try {
return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass()));
} catch (\InvalidArgumentException $e) {
return null;
}
} }
private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array
@ -226,6 +226,10 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
return null; return null;
} }
return array($this->docBlockFactory->create($reflectionMethod, $this->contextFactory->createFromReflector($reflectionMethod)), $prefix); try {
return array($this->docBlockFactory->create($reflectionMethod, $this->contextFactory->createFromReflector($reflectionMethod)), $prefix);
} catch (\InvalidArgumentException $e) {
return null;
}
} }
} }

View File

@ -184,6 +184,29 @@ class PhpDocExtractorTest extends TestCase
{ {
$this->assertNull($this->extractor->getShortDescription(EmptyDocBlock::class, 'foo')); $this->assertNull($this->extractor->getShortDescription(EmptyDocBlock::class, 'foo'));
} }
public function dockBlockFallbackTypesProvider()
{
return array(
'pub' => array(
'pub', array(new Type(Type::BUILTIN_TYPE_STRING)),
),
'protAcc' => array(
'protAcc', array(new Type(Type::BUILTIN_TYPE_INT)),
),
'protMut' => array(
'protMut', array(new Type(Type::BUILTIN_TYPE_BOOL)),
),
);
}
/**
* @dataProvider dockBlockFallbackTypesProvider
*/
public function testDocBlockFallback($property, $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property));
}
} }
class EmptyDocBlock class EmptyDocBlock

View File

@ -0,0 +1,64 @@
<?php
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
/**
* PhpDocExtractor should fallback from property -> accessor -> mutator when looking up dockblocks.
*
* @author Martin Rademacher <mano@radebatz.net>
*/
class DockBlockFallback
{
/** @var string $pub */
public $pub = 'pub';
protected $protAcc;
protected $protMut;
public function getPub()
{
return $this->pub;
}
public function setPub($pub)
{
$this->pub = $pub;
}
/**
* @return int
*/
public function getProtAcc()
{
return $this->protAcc;
}
public function setProt($protAcc)
{
$this->protAcc = $protAcc;
}
public function getProtMut()
{
return $this->protMut;
}
/**
* @param bool $protMut
*/
public function setProtMut($protMut)
{
$this->protMut = $protMut;
}
}