Thanks to visit codestin.com
Credit goes to github.com

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

[PropertyInfo] Extract property type from property declaration #31798

wants to merge 15 commits into from

Conversation

tsantos84
Copy link
Contributor

@tsantos84 tsantos84 commented Jun 2, 2019

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

The next minor version of PHP (7.4) will introduce the feature Typed Property where it'll be possible to declare strong type for class properties. With this nice addition, PropertyInfo component is able to extract property type from the property declaration.

class Dummy {
     public int $int;
     public ?string $string;
}

$types = $propertyInfo->getTypes('Dummy', 'int');

/*
  Example Result
  --------------
  array(1) {
    [0] =>
    class Symfony\Component\PropertyInfo\Type (6) {
      private $builtinType          => string(3) "int"
      private $nullable             => bool(false)
      private $class                => null
      private $collection           => bool(false)
      private $collectionKeyType    => NULL
      private $collectionValueType  => NULL
    }
  }
*/

$types = $propertyInfo->getTypes('Dummy', 'string');

/*
  Example Result
  --------------
  array(1) {
    [0] =>
    class Symfony\Component\PropertyInfo\Type (6) {
      private $builtinType          => string(3) "string"
      private $nullable             => bool(true)
      private $class                => null
      private $collection           => bool(false)
      private $collectionKeyType    => NULL
      private $collectionValueType  => NULL
    }
  }
*/

Technical Notes

  • Property type declaration extraction should precede any other kind of extraction as it is the most reliable source of reflection metadata.
  • The unit tests for this PR will run only when PHP 7.4+ is installed and will be skipped otherwise. The .travisci file was changed to add the required PHP version. The 7.4-dev version (aliased as 7.4snapshot) was configured in travis until the stable release is launch.

@nicolas-grekas nicolas-grekas added this to the next milestone Jun 2, 2019
@tsantos84 tsantos84 changed the title Extract type from declaration [PropertyInfo] Extract type from declaration Jun 2, 2019
@tsantos84 tsantos84 changed the title [PropertyInfo] Extract type from declaration [PropertyInfo] Extract property type from declaration Jun 2, 2019
@tsantos84 tsantos84 marked this pull request as ready for review June 2, 2019 20:13
@tsantos84 tsantos84 requested a review from dunglas as a code owner June 2, 2019 20:13
@tsantos84 tsantos84 changed the title [PropertyInfo] Extract property type from declaration [PropertyInfo] Extract property type from property declaration Jun 3, 2019
public int $int;
public ?string $string;
public Dummy $dummy;
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

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

} 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 ($fromDeclaredType = $this->extractFromDeclaredType($class, $property)) {
return $fromDeclaredType;
}

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

@tsantos84
Copy link
Contributor Author

I'm wondering if this feature should allow failure for PHP 7.4-dev on travisci since the PHP7.4 is not officially released. What do you think?

private function extractFromDeclaredType(string $class, string $property)
{
// to be removed as soon as Symfony bumps the minimum PHP Version to 7.4
if (version_compare(PHP_VERSION, '7.4.0-dev', '<')) {
Copy link
Member

Choose a reason for hiding this comment

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

this should use \PHP_VERSION_ID < 70400 instead. This matches our conventions, which are set this way because it allows to have the condition resolved at compile-time (and so OPCache can optimize the code)

@@ -185,6 +189,30 @@ public function isInitializable(string $class, string $property, array $context
return false;
}

private function extractFromDeclaredType(string $class, string $property)
{
// to be removed as soon as Symfony bumps the minimum PHP Version to 7.4
Copy link
Member

Choose a reason for hiding this comment

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

this comment should be removed. Looking for any check on \PHP_VERSION_ID is part of our process to bump the min version, so it will be done. The comment would not be checked.

}

return $type;
}

private function resolveTypeName(string $name, \ReflectionMethod $reflectionMethod): string
private function resolveTypeName(string $name, ?\ReflectionMethod $reflectionMethod, ?\ReflectionProperty $reflectionProperty): string
Copy link
Member

Choose a reason for hiding this comment

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

as we only need ->getDeclaringClass(), which is common on both class, I suggest using a single argument accepting \ReflectionMethod|\ReflectionProperty (and same in extractFromReflectionType). This will keep the code simpler

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Better than contribute with something is learn something contributing.

Copy link
Contributor Author

@tsantos84 tsantos84 Jun 7, 2019

Choose a reason for hiding this comment

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

Actually this doesn't seems to be a valid syntax. Is your suggestion something like this?

private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod|\ReflectionProperty $reflector): Type {...}

Or you are suggesting to not declare this argument with type:

/**
 * @param \ReflectionMethod|\ReflectionProperty $reflector
*/
private function extractFromReflectionType(\ReflectionType $reflectionType, object $reflector): Type {...}

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.

both \ReflectionMethod and \ReflectionProperty implements Reflector interface, so you can declare it like this:

/**
 * @param \ReflectionMethod|\ReflectionProperty $reflector
*/
private function extractFromReflectionType(\ReflectionType $reflectionType, \Reflector $reflector): Type {...}

Copy link
Contributor Author

@tsantos84 tsantos84 Jun 7, 2019

Choose a reason for hiding this comment

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

Yes, both implements \Reflector but \Reflector doesn't have the necessary getDeclaringClass() method. In this case, I don't think that we should use this interface as a typehint.

Choose a reason for hiding this comment

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

In any case It is far better than just object. For more specific typehint @param declaration will be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As both extractFromReflectionType and resolveTypeName are private methods, I'm good to remove any typehint for this argument and declare its type on @param docblock. What do you think?

{
if (null === ($reflection = $reflectionMethod ?? $reflectionProperty)) {
Copy link
Member

Choose a reason for hiding this comment

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

calling the variable $reflector would be better, as that's the naming used by PHP for its interface implemented by all reflection classes (but it does not make sense to use it as typehint, as getDeclaringClass is not part of this interface as other reflection classes don't implement it)

@tsantos84
Copy link
Contributor Author

Could someone point me about travis?

@nicholasruunu
Copy link
Contributor

Could someone point me about travis?

apcu-5.1.16 is not compatible with php 7.4, it needs to install apcu-5.1.17

@ro0NL
Copy link
Contributor

ro0NL commented Mar 4, 2020

cc @dunglas can you have a look here :) this would fix all "Example values" in API platform for input DTOs, a rather annoying bug

i blindly replaced @var with type info, ony to discover this side effect. I prefer merging this PR asap rather than temporarily putting back @var info.

@dunglas
Copy link
Member

dunglas commented Mar 4, 2020

A similar PR has already been merged in master: #34557

Closing this one.

@dunglas dunglas closed this Mar 4, 2020
@nicolas-grekas nicolas-grekas modified the milestones: next, 5.1 May 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants