bug #20154 [PropertyInfo] Fix edge cases in ReflectionExtractor (nicolas-grekas)

This PR was merged into the 2.8 branch.

Discussion
----------

[PropertyInfo] Fix edge cases in ReflectionExtractor

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

This should make ReflectionExtractor a bit more robust.

ping @dunglas (~~don't miss the changed test case~~).

Commits
-------

bffdfad [PropertyInfo] Fix edge cases in ReflectionExtractor
This commit is contained in:
Kévin Dunglas 2016-10-06 11:22:33 +02:00
commit 781c923c93
No known key found for this signature in database
GPG Key ID: 4D04EBEF06AAF3A6
3 changed files with 40 additions and 63 deletions

View File

@ -44,6 +44,13 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
*/ */
public static $arrayMutatorPrefixes = array('add', 'remove'); public static $arrayMutatorPrefixes = array('add', 'remove');
private $supportsParameterType;
public function __construct()
{
$this->supportsParameterType = method_exists('ReflectionParameter', 'getType');
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -134,68 +141,33 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$reflectionParameters = $reflectionMethod->getParameters(); $reflectionParameters = $reflectionMethod->getParameters();
$reflectionParameter = $reflectionParameters[0]; $reflectionParameter = $reflectionParameters[0];
$arrayMutator = in_array($prefix, self::$arrayMutatorPrefixes); if ($this->supportsParameterType) {
if (!$reflectionType = $reflectionParameter->getType()) {
if (method_exists($reflectionParameter, 'getType') && $reflectionType = $reflectionParameter->getType()) { return;
$fromReflectionType = $this->extractFromReflectionType($reflectionType);
if (!$arrayMutator) {
return array($fromReflectionType);
} }
$type = $this->extractFromReflectionType($reflectionType);
$phpType = Type::BUILTIN_TYPE_ARRAY; // HHVM reports variadics with "array" but not builtin type hints
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT); if (!$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType()) {
$collectionValueType = $fromReflectionType; return;
} }
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
if ($reflectionParameter->isArray()) { if (Type::BUILTIN_TYPE_ARRAY === $info[1]) {
$phpType = Type::BUILTIN_TYPE_ARRAY; $type = new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true);
$collection = true; } elseif (Type::BUILTIN_TYPE_CALLABLE === $info[1]) {
} $type = new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull());
if ($arrayMutator) {
$collection = true;
$nullable = false;
$collectionNullable = $reflectionParameter->allowsNull();
} else {
$nullable = $reflectionParameter->allowsNull();
$collectionNullable = false;
}
if (!isset($collection)) {
$collection = false;
}
if (method_exists($reflectionParameter, 'isCallable') && $reflectionParameter->isCallable()) {
$phpType = Type::BUILTIN_TYPE_CALLABLE;
}
if ($typeHint = $reflectionParameter->getClass()) {
if ($collection) {
$phpType = Type::BUILTIN_TYPE_ARRAY;
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
$collectionValueType = new Type(Type::BUILTIN_TYPE_OBJECT, $collectionNullable, $typeHint->name);
} else { } else {
$phpType = Type::BUILTIN_TYPE_OBJECT; $type = new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $info[1]);
$typeClass = $typeHint->name;
} }
} } else {
// Nothing useful extracted
if (!isset($phpType)) {
return; return;
} }
return array( if (in_array($prefix, self::$arrayMutatorPrefixes)) {
new Type( $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
$phpType, }
$nullable,
isset($typeClass) ? $typeClass : null, return array($type);
$collection,
isset($collectionKeyType) ? $collectionKeyType : null,
isset($collectionValueType) ? $collectionValueType : null
),
);
} }
/** /**
@ -213,7 +185,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return; return;
} }
if (method_exists($reflectionMethod, 'getReturnType') && $reflectionType = $reflectionMethod->getReturnType()) { if ($this->supportsParameterType && $reflectionType = $reflectionMethod->getReturnType()) {
return array($this->extractFromReflectionType($reflectionType)); return array($this->extractFromReflectionType($reflectionType));
} }
@ -231,15 +203,15 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
*/ */
private function extractFromReflectionType(\ReflectionType $reflectionType) private function extractFromReflectionType(\ReflectionType $reflectionType)
{ {
$phpTypeOrClass = method_exists($reflectionType, 'getName') ? $reflectionType->getName() : (string) $reflectionType; $phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : $reflectionType->__toString();
$nullable = $reflectionType->allowsNull(); $nullable = $reflectionType->allowsNull();
if ($reflectionType->isBuiltin()) { if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) { $type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true); } elseif ('void' === $phpTypeOrClass) {
} else { $type = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
$type = new Type($phpTypeOrClass, $nullable); } elseif ($reflectionType->isBuiltin()) {
} $type = new Type($phpTypeOrClass, $nullable);
} else { } else {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $phpTypeOrClass); $type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $phpTypeOrClass);
} }

View File

@ -107,6 +107,7 @@ class ReflectionExtractorTest extends \PHPUnit_Framework_TestCase
{ {
return array( return array(
array('foo', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true))), array('foo', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true))),
array('buz', array(new Type(Type::BUILTIN_TYPE_NULL))),
array('bar', array(new Type(Type::BUILTIN_TYPE_INT, true))), array('bar', array(new Type(Type::BUILTIN_TYPE_INT, true))),
array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))),
array('donotexist', null), array('donotexist', null),

View File

@ -20,6 +20,10 @@ class Php71Dummy
{ {
} }
public function getBuz(): void
{
}
public function setBar(?int $bar) public function setBar(?int $bar)
{ {
} }