Skip to content
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

[PHP 7.1] Add support for ReflectionClassConstant (#281) #282

Merged
merged 16 commits into from
Jul 18, 2017

Conversation

POPSuL
Copy link
Contributor

@POPSuL POPSuL commented Jun 7, 2017

#281

Note: I did not understand how testReflectionObjectOverridesAllMethodsInReflectionClass works, therefore I missed it.

Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

String cast and static export aren't covered by tests


class ReflectionClass implements Reflection, \Reflector
{

Copy link
Member

Choose a reason for hiding this comment

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

Revert

$constants = $this->getReflectionConstants();

if (!isset($constants[$name])) {
return 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'd rather have an exception here. null makes it a silent failure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Urgh, that's just horrible :-(

{
$constants = $this->getReflectionConstants();

if (!isset($constants[$name])) {
Copy link
Member

Choose a reason for hiding this comment

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

array_key_exists()


$constants = [];

foreach ($this->node->stmts as $stmt) {
Copy link
Member

Choose a reason for hiding this comment

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

Use an array filter here. A private method where you do it would be best

}
}

$this->cachedReflectionConstants = $constants;
Copy link
Member

Choose a reason for hiding this comment

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

Can return directly on this assignment

*
* @return bool
*/
public function isProtected()
Copy link
Member

Choose a reason for hiding this comment

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

Return hint. Docblock is redundant

Copy link
Contributor

Choose a reason for hiding this comment

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

Would you be for adding coding standard for these?

Copy link
Member

Choose a reason for hiding this comment

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

If a return type can be specified, it must be specified

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree! There is a sniff for that that can check (and complete) that for you.

{
if ($this->isPublic()) {
return '<public>';
} elseif ($this->isPrivate()) {
Copy link
Member

Choose a reason for hiding this comment

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

else not needed

return '<public>';
} elseif ($this->isPrivate()) {
return '<private>';
} elseif ($this->isProtected()) {
Copy link
Member

Choose a reason for hiding this comment

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

Same

} elseif ($this->isProtected()) {
return '<protected>';
}
return '<unknown>';
Copy link
Member

Choose a reason for hiding this comment

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

Why the brackets?

{
private function getComposerLocator() : ComposerSourceLocator
{
global $loader;
Copy link
Member

Choose a reason for hiding this comment

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

Really smelly...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Can you try $loader = require __DIR__ . '/../../../lots-of-../../vendor/autoload.php';?

@Ocramius
Copy link
Member

Ocramius commented Jun 7, 2017

Note: I did not understand how testReflectionObjectOverridesAllMethodsInReflectionClass works, therefore I missed it.

It's just a test that checks that all method calls go through to an underlying better-reflection class. It's just there for API compatibility: if php-src defines new reflection methods, this test catches them.

@POPSuL
Copy link
Contributor Author

POPSuL commented Jun 8, 2017

Also need:

::getModifiers() : int;
::getDeclaringClass() : ReflectionClass
::getDocComment() : ?string

I'll add it later

@POPSuL
Copy link
Contributor Author

POPSuL commented Jun 8, 2017

Huh, I think I'm done.

public function getReflectionConstants() : array
{
if (null === $this->cachedReflectionConstants) {
$this->cachedReflectionConstants = array_reduce(
Copy link
Member

Choose a reason for hiding this comment

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

Just noticed that this code does not consider parent classes and traits - anything that we can do there?

Copy link
Contributor Author

@POPSuL POPSuL Jun 8, 2017

Choose a reason for hiding this comment

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

@Ocramius native reflection not provide access for constants inherited from parents. Current behavior is exactly the same as in the native reflection.

class A
{
    const A = 1;
}
class B
{
    const B = 2;
}
foreach ([A::class, B::class] as $class) {
    echo 'Native ', $class, ':', PHP_EOL;
    $ref = new \ReflectionClass($class);
    foreach ($ref->getReflectionConstants() as $const) {
        echo $const;
    }

    echo 'Better ', $class, ':', PHP_EOL;
    $ref = $reflector->reflect($class);
    foreach ($ref->getReflectionConstants() as $const) {
        echo $const;
    }
}

Output:

Native A:
Constant [ public integer A ] { 1 }
Better A:
Constant [ public integer A ] { 1 }
Native B:
Constant [ public integer B ] { 2 }
Better B:
Constant [ public integer B ] { 2 }

And traits doesn't support constants.

Copy link
Member

Choose a reason for hiding this comment

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

@Ocramius native reflection not provide access for constants inherited from parents. Current behavior is exactly the same as in the native reflection.

We should provide a "clean" implementation in our own code. For instance, we have Roave\BetterReflection\Reflection\ReflectionClass#getImmediateMethods() for methods implemented only at the current inheritance level (without considering ancestors), and then we provide a reflection-compatible API that retrieves all methods, exactly like internal reflection. The same should apply here.

And traits doesn't support constants.

Didn't know this, thanks for clarifying!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Ocramius I added new methods and class to reflection-compat API.
But new features for class constants is not related to this PR, IMHO.

Copy link
Member

Choose a reason for hiding this comment

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

But new features for class constants is not related to this PR, IMHO.

It's just in line with the package style: it's Better Reflection, not just reflection ;-)

@TomasVotruba
Copy link
Contributor

TomasVotruba commented Jun 26, 2017

@Ocramius We depend on this PR in ApiGen and after 2 weeks there is probably something missing. What needs to be done here?

@Ocramius
Copy link
Member

@TomasVotruba I simply didn't see the pushes. A rebase is needed, then another round of reviews

@TomasVotruba
Copy link
Contributor

@Ocramius Allright, thanks for update.

@POPSuL Could you look on it please?

@POPSuL
Copy link
Contributor Author

POPSuL commented Jun 26, 2017

Hmmm... Strange conflict... I check this tomorrow.

@POPSuL POPSuL force-pushed the issue/php7-const-visibility branch from 9a7de41 to 2ee8f98 Compare June 26, 2017 23:26
@POPSuL
Copy link
Contributor Author

POPSuL commented Jun 29, 2017

@Ocramius rebased 2 days ago ;)

*/
public function getDocComment() : string
{
if (!$this->node->hasAttribute('comments')) {
Copy link
Member

Choose a reason for hiding this comment

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

Please see #284 about the contents of this method

use \ReflectionClassConstant as CoreReflectionClassConstant;
use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant;

class ReflectionClassConstant extends CoreReflectionClassConstant
Copy link
Member

Choose a reason for hiding this comment

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

👍

{
$this->betterClassConstant = $betterClassConstant;
}

Copy link
Member

Choose a reason for hiding this comment

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

Empty line to be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved


class ReflectionClassConstant extends CoreReflectionClassConstant
{
private $betterClassConstant;
Copy link
Member

Choose a reason for hiding this comment

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

Docblock

return null;
}

return $constants[$name];
Copy link
Member

Choose a reason for hiding this comment

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

return $this->getReflectionConstants()[$name] ?? null

private $valueCached = false;

/**
* @var mixed const value
Copy link
Member

Choose a reason for hiding this comment

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

Constants are not mixed: they can contain int|float|array|string|bool|null - all related docblocks need alignment to this too

*/
public function getValue()
{
if (false === $this->valueCached) {
Copy link
Member

Choose a reason for hiding this comment

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

Consider flipping over the conditional, so the processing comes after an early return

*/
public function getModifiers() : int
{
return $this->node->flags === 0 ? Class_::MODIFIER_PUBLIC : $this->node->flags;
Copy link
Member

Choose a reason for hiding this comment

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

Is 0 considered public at all times?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should store the correct modifier if no modifier is defined (public) instead? How much does that go against the original reflection API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If modifier not provided (just const A = 'b') visibility is public.
Original behavior I'll check tomorrow.

*/
public function getDeclaringClass() : ReflectionClass
{
return $this->owner;
Copy link
Member

Choose a reason for hiding this comment

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

What about non-class constants?

<?php

namespace Foo {
    const BAR = 'baz';
}

namespace {
    var_dump(\Foo\BAR);
}

https://3v4l.org/C0hEF

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PHP doesn't have reflection for constants.
ReflectionClassConstant can be instantiated only on ReflectionClass and only for constant related to "this" class.

Copy link
Member

Choose a reason for hiding this comment

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

This was further discussed later on. Indeed, further work on non-class constants will not land in this patch 👍

if ($this->isProtected()) {
return 'protected';
}
return '';
Copy link
Member

Choose a reason for hiding this comment

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

Should we even consider this scenario?

@POPSuL
Copy link
Contributor Author

POPSuL commented Jul 14, 2017

+1 week... :(

@Ocramius
Copy link
Member

@POPSuL patience - you're lucky it's not one of the "3 year old PRs" in my backlog ;-)

@asgrim did we decide something about namespaced constants?

namespace Foo {
    const BAR = 'baz';
}

@asgrim
Copy link
Member

asgrim commented Jul 18, 2017

@Ocramius this is specifically to do with class constants, so not worried about defined or namespaced constants here I think.

My onl concern here is if a new API is introduced into PHP that allows reflection of constants (whether specific to class constants like this) or even all "forms" of constants. This API would then conflict with this, so if that were the case, we'd need to release a new major as BC would be broken. Apart from that 👍 from me.

Constant [ integer MY_CONST_1 ] { 123 }
Constant [ integer MY_CONST_2 ] { 234 }
- Constants [5] {
Constant [ public integer MY_CONST_1 ] { 123 }
Copy link
Member

Choose a reason for hiding this comment

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

Does this information exist when exporting a reflection in core PHP?

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

Copy link
Member

Choose a reason for hiding this comment

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

👍


return $this->cachedReflectionConstants = array_reduce(
$this->node->stmts,
[$this, 'processReflectionConstants'],
Copy link
Member

Choose a reason for hiding this comment

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

This method can just be inlined here I reckon, it's only used the once and I can't see it being re-usable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@Ocramius
Copy link
Member

My onl concern here is if a new API is introduced into PHP that allows reflection of constants (whether specific to class constants like this) or even all "forms" of constants.

We would simply NOT release that part as a Roave\BetterReflection\Reflection\Adapter\* class (that's the bit that is affected by BC issues if PHP-SRC releases new reflection functionality)

@asgrim
Copy link
Member

asgrim commented Jul 18, 2017

Actually, that's a good point. The Adapter class for this is totally unnecessary at this time :)

@Ocramius
Copy link
Member

@asgrim can you create an issue for the namespaced (non-class) constant then? Not to be tackled here at this point.

Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Thanks @POPSuL! I'm merging after applying a few nitpicks :-)

*
* @return ReflectionClassConstant[]
*/
public function getReflectionConstants() : array
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: this block is to be changed at merge time (not a reduce operation)

*/
public function getDeclaringClass() : ReflectionClass
{
return $this->owner;
Copy link
Member

Choose a reason for hiding this comment

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

This was further discussed later on. Indeed, further work on non-class constants will not land in this patch 👍

*
* @link http://php.net/manual/en/reflector.export.php
* @return string
* @since 5.0
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: fix comment on merge

*
* @link http://php.net/manual/en/reflector.tostring.php
* @return string
* @since 5.0
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: fix comment on merge

@Ocramius Ocramius removed the WIP label Jul 18, 2017
@Ocramius Ocramius self-assigned this Jul 18, 2017
@Ocramius Ocramius added this to the 2.0.0 milestone Jul 18, 2017
@Ocramius
Copy link
Member

Just referencing it for confused folks: ReflectionClassConstant exists - see https://bugs.php.net/bug.php?id=74261

@asgrim
Copy link
Member

asgrim commented Jul 18, 2017

Ah yes, confused me indeed. Couldn't find documentation, so disregard my earlier comment about removing the Adapter. Does the API definitely match? How do we verify compatibility without checking the src?

@Ocramius
Copy link
Member

@asgrim there is a ReflectionClassConstantTest - I'm verifying it.

@Ocramius Ocramius merged commit 926a5f6 into Roave:master Jul 18, 2017
Ocramius added a commit that referenced this pull request Jul 18, 2017
…array_map` and `array_combine` sequence
Ocramius added a commit that referenced this pull request Jul 18, 2017
Ocramius added a commit that referenced this pull request Jul 18, 2017
Ocramius added a commit that referenced this pull request Jul 18, 2017
Ocramius added a commit that referenced this pull request Jul 18, 2017
Ocramius added a commit that referenced this pull request Jul 18, 2017
@Ocramius
Copy link
Member

Fixed the nitpicks and merged into master at 44fdc0a, thanks @POPSuL!

TomasVotruba pushed a commit to ApiGen/ApiGen that referenced this pull request Jul 21, 2017
* [WIP] Initial support for ReflectionConstant from Roave/BetterReflection#282

* [WIP] Added descriptions and deprecations for constants

* [WIP] Fixed broken tests

* Added constants to stub

* Fixed ClassConstantReflection::getAnnotations() and cs

* Fixed tests and cs

* Implemented getStart/EndLine for class constants and properties
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.

4 participants