Skip to content

Import useful reflection classes #122

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

Merged
merged 1 commit into from
Sep 14, 2020
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"

- name: "Install dependencies with composer"
run: "composer install --no-interaction --no-progress"
run: " COMPOSER_ROOT_VERSION=2.0 composer install --no-interaction --no-progress"

- name: "Run a static analysis with vimeo/psalm"
run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)"
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
"doctrine/annotations": "^1.0",
"doctrine/cache": "^1.0",
"doctrine/collections": "^1.0",
"doctrine/event-manager": "^1.0",
"doctrine/reflection": "^1.2"
"doctrine/event-manager": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^0.11",
"doctrine/coding-standard": "^6.0",
"doctrine/common": "^3.0",
Copy link
Member Author

Choose a reason for hiding this comment

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

So this is a circular dependency… yuck! It was a dev dependency in doctrine/reflection, not sure why… should I move it to a regular requirement?

Copy link
Member

@morozov morozov Jul 29, 2020

Choose a reason for hiding this comment

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

Can you check what will fail if this dependency is removed? A dev dependency of one package is not necessarily a dev dependency of the one that depends on it. It might have been used by the reflection package to test other classes than the ones being carried over.

should I move it to a regular requirement?

As long as neither this package nor the reflection had a production dependency on the common package, it doesn't look like the right thing to do.

Copy link
Member Author

Choose a reason for hiding this comment

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

I forgot to add it at first and it failed 🙂

"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"vimeo/psalm": "^3.11"
},
Expand Down
170 changes: 91 additions & 79 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lib/Doctrine/Persistence/Mapping/RuntimeReflectionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Doctrine\Persistence\Mapping;

use Doctrine\Common\Reflection\RuntimePublicReflectionProperty;
use Doctrine\Common\Reflection\TypedNoDefaultReflectionProperty;
use Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty;
use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Doctrine\Persistence\Reflection;

use Doctrine\Common\Proxy\Proxy;
Copy link
Member

@morozov morozov Jul 29, 2020

Choose a reason for hiding this comment

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

So this is where the package depends on the common library. It doesn't require it but supports it (hence the test). Would it make sense to just make the test skip if the library is not loaded and add it as a suggestion?

I believe there should be consistency between the dependencies of the code and the dependencies of the tests that cover that code. Not that the code doesn't have a dependency while the test does.

Copy link
Member Author

Choose a reason for hiding this comment

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

So to be clear, you would add composer require --dev doctrine/common to the CI script? In the end, the difference it would make would be that contributors would run part of the tests and would not have to download that lib, at the expense of slightly longer builds in our CI, so… I'm not really convinced but can do the change. Let's see if other contributors feel strongly one way or another?

Copy link
Member

@morozov morozov Jul 29, 2020

Choose a reason for hiding this comment

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

So to be clear, you would add composer require --dev doctrine/common to the CI script?

Yes, this was the idea. Technically, the development of the script doesn't strictly depend on this package.

For instance, DBAL has code and the tests that depend on certain PHP extensions. If those extensions are not available, the tests are skipped. How is it different from this case?

I see what you're saying and I agree that eventually, we are adding the dependency on CI instead of having it added in the repository.

Copy link
Member Author

Choose a reason for hiding this comment

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

How is it different from this case?

Here, it doesn't depend on extensions. Extensions are more involving to install: you have to be root, composer can't do it for you, and in some cases you might even not have a system package for them. That's why it makes a lot of sense to proceed like this on the DBAL. doctrine/common has 2 dependencies: this very lib, and php, so it means no extra dep. It's just not worth it IMO.

Copy link
Member

Choose a reason for hiding this comment

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

I can live with composer require --dev doctrine/common or adding it to the dev dependencies. I've seen both ways in different projects.

Copy link
Member

@morozov morozov Aug 4, 2020

Choose a reason for hiding this comment

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

If I may summarize it, if there's no reason for the change, let's not make it (i.e. keep the development dependency).

Copy link
Member

Choose a reason for hiding this comment

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

The other option is to copy the interface here and add a autoload-dev. Since its just a marker interface with no methods, there is no risk of breaking with or without doctrine/common.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added the requirement under the require-dev section, so it's not like there is an impact on the users.

use ReflectionProperty;

/**
* PHP Runtime Reflection Public Property - special overrides for public properties.
*/
class RuntimePublicReflectionProperty extends ReflectionProperty
{
/**
* {@inheritDoc}
*
* Checks is the value actually exist before fetching it.
* This is to avoid calling `__get` on the provided $object if it
* is a {@see \Doctrine\Common\Proxy\Proxy}.
*/
public function getValue($object = null)
{
$name = $this->getName();

if ($object instanceof Proxy && ! $object->__isInitialized()) {
$originalInitializer = $object->__getInitializer();
$object->__setInitializer(null);
$val = $object->$name ?? null;
$object->__setInitializer($originalInitializer);

return $val;
}

return isset($object->$name) ? parent::getValue($object) : null;
}

/**
* {@inheritDoc}
*
* Avoids triggering lazy loading via `__set` if the provided object
* is a {@see \Doctrine\Common\Proxy\Proxy}.
*
* @link https://bugs.php.net/bug.php?id=63463
*/
public function setValue($object, $value = null)
{
if (! ($object instanceof Proxy && ! $object->__isInitialized())) {
parent::setValue($object, $value);

return;
}

$originalInitializer = $object->__getInitializer();
$object->__setInitializer(null);
parent::setValue($object, $value);
$object->__setInitializer($originalInitializer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Doctrine\Persistence\Reflection;

use ReflectionProperty;

/**
* PHP Typed No Default Reflection Property - special override for typed properties without a default value.
*/
class TypedNoDefaultReflectionProperty extends ReflectionProperty
{
/**
* {@inheritDoc}
*
* Checks that a typed property is initialized before accessing its value.
* This is necessary to avoid PHP error "Error: Typed property must not be accessed before initialization".
* Should be used only for reflecting typed properties without a default value.
*/
public function getValue($object = null)
{
return $object !== null && $this->isInitialized($object) ? parent::getValue($object) : null;
}

/**
* {@inheritDoc}
*
* Works around the problem with setting typed no default properties to
* NULL which is not supported, instead unset() to uninitialize.
*
* @link https://github.com/doctrine/orm/issues/7999
*/
public function setValue($object, $value = null)
{
if ($value === null && $this->hasType() && ! $this->getType()->allowsNull()) {
$propertyName = $this->getName();

$unsetter = function () use ($propertyName) {
unset($this->$propertyName);
};
$unsetter = $unsetter->bindTo($object, $this->getDeclaringClass()->getName());
$unsetter();

return;
}

parent::setValue($object, $value);
}
}
8 changes: 8 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<RedundantCondition>
<errorLevel type="suppress">
<!-- see https://github.com/JetBrains/phpstorm-stubs/pull/877 -->
<file name="lib/Doctrine/Persistence/Reflection/TypedNoDefaultReflectionProperty.php"/>
</errorLevel>
</RedundantCondition>
</issueHandlers>
</psalm>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace Doctrine\Tests\Persistence\Mapping;

use Doctrine\Common\Reflection\RuntimePublicReflectionProperty;
use Doctrine\Persistence\Mapping\MappingException;
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
use Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use function count;
Expand Down
Loading