8000 [PropertyInfo] Extract property type from property declaration by tsantos84 · Pull Request #31798 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[PropertyInfo] Extract property type from property declaration #31798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ matrix:
env: deps=high
- php: 7.3
env: deps=low
- php: 7.4snapshot

fast_finish: true

Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/PropertyInfo/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

4.4.0
-----

* Added the ability to extract property type from the property declaration

4.3.0
-----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ public function getProperties($class, array $context = [])
*/
public function getTypes($class, $property, array $context = [])
{
if ($fromDeclaredType = $this->extractFromDeclaredType($class, $property)) {
return $fromDeclaredType;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change causing test fails:

PHPUnit 7.4.5 by Sebastian Bergmann and contributors.

Testing Symfony Property Info Component Test Suite
...............................................................  63 / 188 ( 33%)
.....SSS.................................EEEEEEEEEEEEEEEEEEEEEE 126 / 188 ( 67%)
..............................................................  188 / 188 (100%)

Time: 7.72 seconds, Memory: 8.00 MB

There were 22 errors:

1) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #0 ('a', null)
ReflectionException: Property a does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

2) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #1 ('b', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property b does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

3) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #2 ('c', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property c does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

4) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #3 ('d', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property d does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

5) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #4 ('e', null)
ReflectionException: Property e does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

6) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #5 ('f', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property f does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

7) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #6 ('donotexist', null)
ReflectionException: Property donotexist does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

8) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #7 ('staticGetter', null)
ReflectionException: Property staticGetter does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

9) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #8 ('staticSetter', null)
ReflectionException: Property staticSetter does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

10) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #9 ('self', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property self does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

11) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractors with data set #10 ('realParent', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property realParent does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:155

12) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #0 ('foo', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property foo does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

13) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #1 ('bar', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property bar does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

14) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #2 ('baz', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property baz does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

15) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #3 ('buz', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property buz does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

16) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #4 ('biz', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property biz does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

17) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp7Type with data set #5 ('donotexist', null)
ReflectionException: Property donotexist does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:180

18) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp71Type with data set #0 ('foo', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property foo does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:200

19) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp71Type with data set #1 ('buz', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property buz does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:200

20) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp71Type with data set #2 ('bar', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property bar does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:200

21) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp71Type with data set #3 ('baz', array(Symfony\Component\PropertyInfo\Type Object (...)))
ReflectionException: Property baz does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:200

22) Symfony\Component\PropertyInfo\Tests\Extractor\ReflectionExtractorTest::testExtractPhp71Type with data set #4 ('donotexist', null)
ReflectionException: Property donotexist does not exist

/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:205
/usr/src/app/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php:112
/usr/src/app/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php:200

ERRORS!
Tests: 188, Assertions: 251, Errors: 22, Skipped: 3.

Copy link
@mshavliuk mshavliuk Jun 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can easily reproduce this tests by running this in src/Symfony/Component/PropertyInfo:

docker run --rm -it -v $(pwd):/var/www/html devilbox/php-fpm-7.4 bash
composer install # (if you don't have yet)
composer require --dev symfony/test-pack 
vendor/bin/simple-phpunit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if ($fromMutator = $this->extractFromMutator($class, $property)) {
return $fromMutator;
}
Expand Down Expand Up @@ -185,6 +189,28 @@ public function isInitializable(string $class, string $property, array $context
return false;
}

private function extractFromDeclaredType(string $class, string $property)
{
if (version_compare(\PHP_VERSION_ID, 70400, '<')) {
return null;
}

try {
$reflectionClass = new \ReflectionClass($class);
$reflectionProperty = $reflectionClass->getProperty($property);
} catch (\ReflectionException $e) {
return null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent test fails you can add following code here:

if (!$reflectionClass->hasProperty($property)) {
    return null;
}

Copy link
@mshavliuk mshavliuk Jun 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you can expand try-catch block for the whole function content (to prevent errors, which can happen while calling hasType, getProperty, getType etc. which can throw ReflectionException).
I think the best option - add either hasProperty or expand try-catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used the same try/catch block where I create the reflection class to get the property, and if it doesn't exist, it'll return null

if (!$reflectionProperty->hasType()) {
return null;
}

$reflectionType = $reflectionProperty->getType();

return [$this->extractFromReflectionType($reflectionType, $reflectionProperty)];
}

/**
* @return Type[]|null
*/
Expand Down Expand Up @@ -287,7 +313,13 @@ private function extractFromDefaultValue(string $class, string $property)
return [new Type(static::MAP_TYPES[$type] ?? $type)];
}

private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod): Type
/**
* @param \ReflectionType $reflectionType
* @param \ReflectionMethod|\ReflectionProperty $reflector
*
* @return Type
*/
private function extractFromReflectionType(\ReflectionType $reflectionType, $reflector): Type
{
$phpTypeOrClass = $reflectionType->getName();
$nullable = $reflectionType->allowsNull();
Expand All @@ -298,19 +330,37 @@ private function extractFromReflectionType(\ReflectionType $reflectionType, \Ref
$type = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
} elseif ($reflectionType->isBuiltin()) {
$type = new Type($phpTypeOrClass, $nullable);
} elseif ($reflector instanceof \ReflectionMethod || $reflector instanceof \ReflectionProperty) {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflector));
} else {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
throw new \InvalidArgumentException(
'$reflector should be an instance of ReflectionMethod or ReflectionProperty, '
.(\is_object($reflector) ? \get_class($reflector) : \gettype($reflector)).' given'
);
}

return $type;
}

private function resolveTypeName(string $name, \ReflectionMethod $reflectionMethod): string
/**
* @param string $name
* @param \ReflectionMethod|\ReflectionProperty $reflector
*
* @return string
*/
private function resolveTypeName(string $name, $reflector): string
{
if (!($reflector instanceof \ReflectionMethod || $reflector instanceof \ReflectionProperty)) {
throw new \InvalidArgumentException(
'$reflector should be an instance of ReflectionMethod or ReflectionProperty, '
.(\is_object($reflector) ? \get_class($reflector) : \gettype($reflector)).' given'
);
}

if ('self' === $lcName = strtolower($name)) {
return $reflectionMethod->getDeclaringClass()->name;
return $reflector->getDeclaringClass()->name;
}
if ('parent' === $lcName && $parent = $reflectionMethod->getDeclaringClass()->getParentClass()) {
if ('parent' === $lcName && $parent = $reflector->getDeclaringClass()->getParentClass()) {
return $parent->name;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php74Dummy;
use Symfony\Component\PropertyInfo\Type;

/**
Expand Down Expand Up @@ -228,6 +231,31 @@ public function defaultValueProvider()
];
}

/**
* @dataProvider declaredTypeProvider
*/
public function testExtractFromDeclaredType($property, $type)
{
if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
$this->markTestSkipped('Extracting type from declared type is enabled only on PHP 7.4+');
}

$this->assertEquals($type, $this->extractor->getTypes(Php74Dummy::class, $property, []));
}

public function declaredTypeProvider()
{
return [
['int', [new Type(Type::BUILTIN_TYPE_INT, false)]],
['string', [new Type(Type::BUILTIN_TYPE_STRING, true)]],
['dummy', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)]],
['optionalDummy', [new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class)]],
['iterable', [new Type(Type::BUILTIN_TYPE_ITERABLE, false)]],
['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, Php74Dummy::class)]],
['parent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]],
];
}

/**
* @dataProvider getReadableProperties
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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\Component\PropertyInfo\Tests\Fixtures;

/**
* Class Php74Dummy
*
* @author Tales Santos <tales.augusto.santos@gmail.com>
*/
class Php74Dummy extends ParentDummy
{
public int $int;
public ?string $string;
public Dummy $dummy;
public parent $parent;
public self $self;
public ?Dummy $optionalDummy;
Copy link
@mshavliuk mshavliuk Jun 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you forgot about "callable" property, which is mention in test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this test case should be removed because declaring callable for a property is not allowed. https://wiki.php.net/rfc/typed_properties_v2

public iterable $iterable;
}
0