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

Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Conversation

@sspat
Copy link
Contributor

@sspat sspat commented Dec 2, 2019

Implementation for issue #23
See discussion in doctrine/orm#7857

As suggested by @beberlei, the \Doctrine\Common\Reflection\TypedNoDefaultReflectionProperty class was introduced.
It will be returned in doctrine/persistence package class RuntimeReflectionService::getAccessibleProperty if the property is typed and has no default value. Something like this:

/**
 * {@inheritDoc}
 */
public function getAccessibleProperty(string $class, string $property) : ?ReflectionProperty
{
    $reflectionProperty = new ReflectionProperty($class, $property);

    if (! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) {
        $reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property);
    } elseif ($reflectionProperty->isPublic()) {
        $reflectionProperty = new RuntimePublicReflectionProperty($class, $property);
    }

    $reflectionProperty->setAccessible(true);

    return $reflectionProperty;
}

Also updated phpunit config as suggested in the discussion in the same issue #23.
This allows skipping tests containing php 7.4 specific syntax on builds with lesser php versions.
Also updated travis-ci.yml Lint job to ignore such tests by phpstan analysis, if the php version is less than 7.4

@alcaeus alcaeus added this to the 1.1.0 milestone Dec 2, 2019
@alcaeus alcaeus self-assigned this Dec 2, 2019
Copy link
Member

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

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

Logic LGTM, not sure about changes to the PHPStan build stage on travis-ci.

.travis.yml Outdated

- stage: Lint
script: vendor/bin/phpstan analyse -l 3 -c phpstan.neon lib tests
before_script: if grep -si 7.4 <<< "$TRAVIS_PHP_VERSION"; then export TESTS_PATH="tests/Doctrine/Tests tests/Doctrine/Tests_PHP74"; else export TESTS_PATH=tests/Doctrine/Tests; fi
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to avoid this conditional? Does PHPStan stumble over these test files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, now as I think of it, there is a better way to solve this.

The problem is, that the PHPStan version currently used depends on an older version of nikic/php-parser that does not know the new php 7.4 tokens.

So another solution is to update PHPStan with it's dependecies.

I reverted the changes to the Lint stage of the CI config and updated the packages.
Also fixed two issues that the new version detected.

@alcaeus
Copy link
Member

alcaeus commented Dec 2, 2019

@sspat see #25 - feel free to ignore phpstan for now. I can take care of it when merging everything, just want to figure out #11 first.

@sspat
Copy link
Contributor Author

sspat commented Dec 2, 2019

@alcaeus ok, just updated PHPStan myself, I'll revert those changes then?

@alcaeus
Copy link
Member

alcaeus commented Dec 2, 2019

Yep, no further work necessary on phpstan for now, I'll figure this out when merging.

*/
public function getValue($object = null)
{
$name = $this->getName();
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 most certainly be cached locally (in the instance). Alternatively, using $this->name in the expression below would work too.

Yes, it's horrible, but this is really performance sensitive

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should most certainly be cached locally (in the instance). Alternatively, using $this->name in the expression below would work too.

Yes, it's horrible, but this is really performance sensitive

But what about \Doctrine\Common\Reflection\RuntimePublicReflectionProperty? It is using the same mechanism and a public property on an entity is still a much more common scenario than a typed property, caching should be implemented there too. Maybe a separate issue for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And looking at the tests, adding caching would break the current behavior.
For example in

public function testGetValue() : void
{
$object = new RuntimePublicReflectionPropertyTestClass();
$reflProperty = new RuntimePublicReflectionProperty(RuntimePublicReflectionPropertyTestClass::class, 'test');
self::assertSame('testValue', $reflProperty->getValue($object));
unset($object->test);
self::assertNull($reflProperty->getValue($object));
}

The RuntimePublicReflectionProperty would not follow the changes to the reflected object's properties, if it was caching the value on first access to the property.

Copy link
Contributor Author

@sspat sspat Dec 3, 2019

Choose a reason for hiding this comment

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

Also looking at

https://github.com/doctrine/persistence/blob/779661acceefe09bd0e709b4cfe65ec0213f0d5c/lib/Doctrine/Persistence/Mapping/RuntimeReflectionService.php#L60-L74

The class introduced in this PR will be returned from this method too, and if we add caching only to this new class, it will still break the behavior of the getAccessibleProperty method, as the reflection property classes returned by it would not cache and do track the reflected object changes for some properties and the opposite for other properties.

{
$name = $this->getName();

return isset($object->$name) ? parent::getValue($object) : null;
Copy link
Member

Choose a reason for hiding this comment

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

I think an isset($object->$name) will initialize that property starting from ORM 3, because ProxyManager intercepts isset() calls via __isset().

Instead, ReflectionProperty#isInitialized() should be used.

Copy link
Member

Choose a reason for hiding this comment

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

And yes: more performance impact :-(

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 can do this, but again it looks to me as a separate issue, this behavior was copied from another class in the package \Doctrine\Common\Reflection\RuntimePublicReflectionProperty, it has issues then too.
Thanks for the review!

Copy link
Member

Choose a reason for hiding this comment

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

In general, accessing typed properties via isset() is not side-effect-free, and will break on anything declaring __isset (not just because of ProxyManager)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I understand the problem now, will fix it in the new class, it will also make it's purpose more explicit. Thank you!

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

public function getValue($object = null)
{
return $object !== null && $this->isInitialized($object) ? parent::getValue($object) : null;
}

@alcaeus alcaeus force-pushed the add_reflection_typed_no_default_property_class branch from f45415e to 9acbb8c Compare December 3, 2019 15:24
@alcaeus
Copy link
Member

alcaeus commented Dec 3, 2019

@sspat I've rebased your branch on top of master and dropped the phpstan update and revert commits.

@Ocramius can you please give this another look? Thanks!

@alcaeus alcaeus requested a review from Ocramius December 3, 2019 15:25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants