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

Scrutinizer fixes #76

Merged
merged 9 commits into from
Aug 8, 2017
10 changes: 9 additions & 1 deletion src/NodeVisitor/RootNamespaceNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@
class RootNamespaceNormalizer extends NodeVisitorAbstract
{
/**
* {@inheritdoc}
* Called once before traversal.
*
* Return value semantics:
* * null: $nodes stays as-is
* * otherwise: $nodes is set to the return value
*
* @param Node[] $nodes Array of nodes
*
* @return null|Node[] Array of nodes
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 is really a failed attempt to fix a false-positive in Scrutinizer. I merely copied the original function docblock from PhpParser. The issue is that Scrutinizer is letting the less specific type hint array take precedence over the compatible and more specific @param Node[], and saying that array does not satisfy the specification @return null|Node[]. I expect this change should actually be reverted, but I didn't want to do so without feedback.

As far as the false-positive, I'm trying to notify Scrutinizer about it, but haven't gotten any confirmation yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thumbing myself up purely so I remember to revert this change: 👍

*/
public function beforeTraverse(array $nodes)
{
Expand Down
3 changes: 3 additions & 0 deletions src/ReflectionFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class ReflectionFile
*/
public function __construct($fileName, $topLevelNodes = null)
{
if (!is_string($fileName)) {
Copy link

Choose a reason for hiding this comment

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

It seems, that you've started to change more code. I'd recommend doing this in another PRs.

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 was a change from my original commit from July 3rd before I ever issued this PR. I'm slightly confused.

Copy link

Choose a reason for hiding this comment

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

Interesting. I've reviewed only changes that Scrutinizer CI reported as fixes to existing issues. Probably this change fixed nothing of Scrutinizer CI reported bugs.

In either case I think it's too much to check filename to be a string. Likely nobody would be crazy enough to pass non-string in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only reason I made this addition was that it was flagged by Scrutinizer:

Documentation Bug introduced 30 days ago by @lisachenko
It seems like $fileName can also be of type array or Boolean. However, the property $fileName is declared as type string. Maybe add an additional type check?

I think this based on type variations in the calling functions, despite this function only ever being called with string. I expect the other change that might fix this is isolating the parameter into a distinct, string-only variable that is used when calling this method. I think this type check is the easier solution.

Copy link

Choose a reason for hiding this comment

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

Then better to:

  1. revert this change
  2. in another PR address cases, when method can be called with non-string argument

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The purpose of this PR was to address the Scrutinizer issues. This does that and it was in the PR from the beginning.

I'm confused why this is suddenly an issue?

Let me investigate the calling code to see if that is more easily addressed than this. I'll get back to you.

Copy link

Choose a reason for hiding this comment

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

  1. huge PRs are pain to review (this PR is becoming larger and larger)
  2. the solution you've chosen for Scrutinizer CI reported problem isn't correct one

The Scrutinizer CI told that multiple types could come in and you've restricted to type check instead of actually investigating places from where multiple types could come in and deciding on fixing these places instead. This could be a lots of changes and that's why I've suggested another PR.

P.S.
You don't need to put ALL fixing in single PR. Better to send smaller manageable PRs. Right now this PR has large discussion that already is hard to track what's going on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok... I searched the code, and (outside of tests/) nothing within ParserReflection instantiates Go\ParserReflection\ReflectionFile, and ever use of it is annotated as string: The constructor is annotated @param string, the class property is annotated @var string, and the accessor is annotated @return string. I can only assume that this a plea for enforcing type safety rather than warning that badly typed data is being redirected here. I happen to be a fan of enforcing types where appropriate, so I see no harm in leaving this change, and the matching one for ReflectionFileNamespace intact.

👎 Keeping change in PR pending @lisachenko's approval.

Copy link

Choose a reason for hiding this comment

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

Enforcing something, that will never happen would only slow down whole code checking process. For that reason I'd rather not check unless somebody stumbles upon this and reports it.

Copy link

Choose a reason for hiding this comment

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

Let's keep it then. I hope this won't be a performance issue.

throw new \InvalidArgumentException(sprintf('$fileName must be a string, but a %s was passed', gettype($fileName)));
}
$fileName = PathResolver::realpath($fileName);
$this->fileName = $fileName;
$this->topLevelNodes = $topLevelNodes ?: ReflectionEngine::parseFile($fileName);
Expand Down
3 changes: 3 additions & 0 deletions src/ReflectionFileNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class ReflectionFileNamespace
*/
public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)
{
if (!is_string($fileName)) {
Copy link

Choose a reason for hiding this comment

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

It seems, that you've started to change more code. I'd recommend doing this in another PRs.

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 was a change from my original commit from July 3rd before I ever issued this PR. I'm slightly confused.

Copy link

Choose a reason for hiding this comment

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

Interesting. I've reviewed only changes that Scrutinizer CI reported as fixes to existing issues. Probably this change fixed nothing of Scrutinizer CI reported bugs.

In either case I think it's too much to check filename to be a string. Likely nobody would be crazy enough to pass non-string in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only reason I made this addition was that it was flagged by Scrutinizer:

Documentation Bug introduced 10 months ago by @lisachenko
It seems like $fileName can also be of type array or Boolean. However, the property $fileName is declared as type string. Maybe add an additional type check?

(Same comment as above)

Copy link

Choose a reason for hiding this comment

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

Then better to:

  1. revert this change
  2. in another PR address cases, when method can be called with non-string argument

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While there are 3 places in the code where Go\ParserReflection\ReflectionFileNamespace is instantiated, and two of them are called with filename coming from Object(Go\ParserReflection\ValueResolver\NodeExpressionResolver)->context->getFileName() where context isn't a strongly typed property, I believe the analysis from Go\ParserReflection\ReflectionFile's constructor still holds: It's a good idea to do type checking where appropriate, and I think this can stay, unless @lisachenko wants me to yank it.

Copy link

Choose a reason for hiding this comment

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

What is the 3rd place, can you post a link to that code line? If total call count in that 3rd place is much smaller than sum of other 2 places, then from performance point it would be better to type cast before calling this method instead of checking in constructor.

Same is valid for other similar place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok... I'm happy to show you the three places that call this constructor, but the fact that you're asking indicates a failure to communicate on my part. I apologize for being unclear.

Here are the 3 places that call this constructor:

.../parser-reflection/src/ReflectionFile.php:

  153                  $namespaceName = $topLevelNode->name ? $topLevelNode->name->toString() : '';
  154  
  155:                 $namespaces[$namespaceName] = new ReflectionFileNamespace(
  156                      $this->fileName,
  157                      $namespaceName,

.../parser-reflection/src/ValueResolver/NodeExpressionResolver.php:

  231                  $fileName      = $this->context->getFileName();
  232                  $namespaceName = $this->resolveScalarMagicConstNamespace();
  233:                 $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
  234                  if ($fileNamespace->hasConstant($constantName)) {
  235                      $constantValue = $fileNamespace->getConstant($constantName);

...

  500              $namespaceName = $this->resolveScalarMagicConstNamespace();
  501  
  502:             $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
  503              return $fileNamespace->getClass($className);
  504          }

That said, I think this largely misses the point. Let me see if I can clear this up a little bit:

  • First, an apology: When you first brought these two changes up as issues, I think I took it a bit personally, and doubled down by adding unit tests for my type checking code. I was hoping that after trying to explain myself, why I made these changes would be clear, and I tried to defer to @lisachenko as an arbitrator. This basically blew up in my face, and I failed to address your concerns about these changes directly. I'm sorry. Mea culpa.

  • Relevance: The only reason I included these checks in this PR was because Scrutinizer flagged them as issues. While I try not to take anything that Scrutinizer says as gospel, in this case it has a point: These are public API entry points, and can be called with invalid arguments. Since we can't add PHP 7 type hints here without breaking PHP 5 compatibility, I felt it was appropriate to add these type checks to fix Scrutinizer's issue.

  • Performance: You have repeatedly brought up the concern of this check impacting performance, but in these two instances it just isn't applicable. Of course I agree that any operation takes some amount overhead. The problem is that these are checks that only occur on the construction of infrequently created objects that often result in loading and parsing a file. Even if the parsed file cache is 80% effective, and we're pulling data from a solid state drive, or even the OS cache, checking the data type of a single function parameter has to be at least 3 to 5 orders of magnitude smaller reading and parsing a file 20% of the time. I agree that it contributes to constructor complexity which impacts maintainability, and both of those need to be kept in check, but this has no appreciable impact on performance.

  • Addressing this from where the method is called: I think this is where our biggest misunderstanding lies.

    • My goal is to eliminate the issues being reported by Scrutinizer.
    • Scrutinizer is reporting two almost identical issues on two constructors with identically defined $fileName parameters.
    • One of those constructors is never called by this project's code, while the other is called three times.
    • If ensuring that all calling code was properly type hinted, then we should expect that the constructor that is never called (so all zero of its callers are properly type-hinted) would not trigger any Scrutinizer issue.
    • Since the uncalled constructor does trigger a Scrutinizer issue, I think it's safe to assume that ensuring the callers are properly type hinted (which I believe they already are) will have any impact on Scrutinizer reporting this issue.
    • Also, as this is a public API, I see no harm in a bit of sanity checking of parameters.
  • Summary: The decision is ultimately yours, so if you ask me to get rid of these two checks, and the unit tests to support them, I will, but I think that asking that really misses the point of this PR.

So, your choices are:

  1. I can leave these two if (is_string()) checks, along with the corresponding unit tests, in place.
  2. Or I can remove them.

Copy link

Choose a reason for hiding this comment

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

1 - keep.

If we try to imagine real life examples, then ReflectionFileNamespace class would only be created once per namespace. And I haven't seen any codebases, where unique namespace count is fare superior (or even equal) to class/interface/trait count in these namespaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your quick response. I hope to have the final commit pushed this afternoon.

throw new \InvalidArgumentException(sprintf('$fileName must be a string, but a %s was passed', gettype($fileName)));
}
$fileName = PathResolver::realpath($fileName);
if (!$namespaceNode) {
$namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName);
Expand Down
3 changes: 2 additions & 1 deletion src/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public function ___debugInfo()
public function __toString()
{
$hasReturnType = $this->hasReturnType();
$returnType = $hasReturnType ? $this->getReturnType() : null;
Copy link

Choose a reason for hiding this comment

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

The $hasReturnType ? $this->getReturnType() : null; can be replaced with just $this->getReturnType();, because internally hasReturnType method is calling getReturnType and checking result via isset.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch 👍

$paramsNeeded = $hasReturnType || $this->getNumberOfParameters() > 0;
$paramFormat = $paramsNeeded ? "\n\n - Parameters [%d] {%s\n }" : '';
$returnFormat = $hasReturnType ? "\n - Return [ %s ]" : '';
Expand Down Expand Up @@ -114,7 +115,7 @@ public function __toString()
$this->getEndLine(),
count($methodParameters),
$paramString,
$hasReturnType ? ReflectionType::convertToDisplayType($this->getReturnType()) : ''
$returnType ? ReflectionType::convertToDisplayType($returnType) : ''
Copy link

Choose a reason for hiding this comment

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

👍 , but also add tests (or post a link in comment if test exists already), that would verify atleast 2 cases:

  1. the return type was null and wasn't present in output
  2. the return type was ReflectionType object and it's string representation was shown in output

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok... Good point on adding tests. As I said, needing to untangle a branch first, but: Note to self... Implement/find relevant test cases. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok... I confirmed that both cases are tested... (I did not flag the specific tests, but they're easy to find.) When $returnType is null the empty string value isn't used, but it prevents the convertToDisplayType() static method call that would be illegal with a null parameter. I am replacing the empty string with a string explaining its purpose.

);
}

Expand Down
4 changes: 2 additions & 2 deletions src/ReflectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ public static function convertToDisplayType(\ReflectionType $type)
'int' => 'integer',
'bool' => 'boolean'
];
$displayType = $type->type;
$displayType = (string)$type;
Copy link

Choose a reason for hiding this comment

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

👍 , but a test is needed (or post link to existing test), that would make a call to convertToDisplayType method with:

  1. native ReflectionType class object
  2. with library's own ReflectionType object, that has type property

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 above... add tests 👍

if (isset($typeMap[$displayType])) {
$displayType = $typeMap[$displayType];
}

$displayType = ltrim($displayType, '\\');

if ($type->allowsNull()) {
if (method_exists($type, 'allowsNull') && $type->allowsNull()) {
Copy link

Choose a reason for hiding this comment

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

Please return the original code, because Scrutinizer CI issue in https://scrutinizer-ci.com/g/goaop/parser-reflection/inspections/eaf283f6-f982-44a4-bb28-51489bcba537/issues/files/src/ReflectionType.php?status=fixed&orderField=path&order=asc&honorSelectedPaths=0 likely is reported because they're using incorrect PHP stubs for class method signature resolution. On the PHP docs (see http://php.net/manual/en/reflectiontype.allowsnull.php) there is allowsNull method and it always was there (as in out ReflectionType class polyfill).

Copy link
Contributor Author

@loren-osborn loren-osborn Jul 26, 2017

Choose a reason for hiding this comment

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

Hmm... I agree that I should revert this code, but for a different reason. I think this is catching the class definition from the polyfill, which had no $this->allowsNull() method. I actually added a dummy $this->allowsNull() method to my polly fill, so this should no longer be an issue. I don't particularly like the dummy method in the polyfill, but I think it's the best solution currently. Either way, this code should no longer be necessary. Thanks for pointing this out. 👍

Copy link

Choose a reason for hiding this comment

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

I think this is catching the class definition from the polyfill, which had no $this->allowsNull() method.

Then I think you need to add stubs in the polyfill class for all 3 methods: http://php.net/manual/en/class.reflectiontype.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.

Fair enough! :-) 👍

$displayType .= ' or NULL';
}

Expand Down
15 changes: 12 additions & 3 deletions src/Traits/ReflectionClassLikeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace Go\ParserReflection\Traits;

use Go\ParserReflection\ReflectionClass;
use ReflectionClass as BaseReflectionClass;
use Go\ParserReflection\ReflectionException;
use Go\ParserReflection\ReflectionMethod;
use Go\ParserReflection\ReflectionProperty;
Expand Down Expand Up @@ -442,7 +443,7 @@ public function getParentClass()
$extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null;
if ($extendsNode instanceof FullyQualified) {
$extendsName = $extendsNode->toString();
$parentClass = class_exists($extendsName, false) ? new parent($extendsName) : new static($extendsName);
$parentClass = class_exists($extendsName, false) ? new BaseReflectionClass($extendsName) : new ReflectionClass($extendsName);
Copy link

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm... Here I'm a bit torn here:

  • I don't like the old code: Scrutinize is right.
    • This is a trait. in theory, it should be includable in any class
    • Traits don't have parents, or even self.
      • PHP did the right thing in interpreting self and parent relative to the trait user
    • This interpretation of self and parent is rather non-obvious, and does nothing to aid code clarity.
    • There is no way to statically analyze this without the class it's being used in, which explains Scrutinize's issue.
  • I don't like the new code either:
    • My changes totally lock this down to inclusion in one specific class: That's wrong too.
    • There really should be specified by the including class. a variable could work, but I think a class method in the using class is the best solution.
  • One of the PRs I'm hoping to submit soon is a factory for reflection classes:
    • ... so I expect this issue to go away soon.

@aik099, for now, I think the best solution is a method in the using class to return the newly created objects. What do you think?

Copy link

Choose a reason for hiding this comment

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

Fact, that trait is used only once and seems like it would be for a long time (unless PHP decides to reflect class/interface/trait using different reflection classes) is a problem that I think needs to be solved.

If we inline trait (copy trait code into the only class using it and drop trait), then problem should go away by itself. This is a major change however (even though not a BC break) and likely a @lisachenko approval is required before making such change.

Actually better to move it out into PR of it's own.

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 was only really suggesting adding a protected function createReflectionObjectForClass() containing only that one trinary expression line. Seems like a pretty minor change to me.

}
$this->parentClass = $parentClass;
}
Expand Down Expand Up @@ -698,8 +699,16 @@ public function isInstance($object)
}

$className = $this->getName();

return $className === get_class($object) || is_subclass_of($object, $className);
$objClass = get_class($object);
Copy link

Choose a reason for hiding this comment

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

Please revert because Scrutinizer CI report (see https://scrutinizer-ci.com/g/goaop/parser-reflection/inspections/eaf283f6-f982-44a4-bb28-51489bcba537/issues/files/src/Traits/ReflectionClassLikeTrait.php?status=fixed&orderField=path&order=asc&honorSelectedPaths=0) mentions https://bugs.php.net/bug.php?id=53727 bug, which was fixed in PHP 5.3 and PHP 5.4, but this library has PHP 5.5+ as minimum required version.

Therefore bug never manifested itself in here.

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 makes sense... I should check if there's a way to turn off a specific issue check for a whole project.

Copy link

Choose a reason for hiding this comment

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

Users with administrative permissions for repo can do it on Scrutinizer CI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

marking with 👍 to remind me to revert.

if ($className === $objClass) {
return true;
}
$objClassRef = new BaseReflectionClass($objClass);
if ($objClassRef->isSubclassOf($className)) {
return true;
}
$thisIsInterface = $this->isInterface();
return $thisIsInterface && $objClassRef->implementsInterface($className);
}

/**
Expand Down
18 changes: 18 additions & 0 deletions src/Traits/ReflectionFunctionLikeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,15 @@ public function getStartLine()
*/
public function getStaticVariables()
{
// In nikic/PHP-Parser < 2.0.0 the default behavior is cloning
// nodes when traversing them. Passing FALSE to the constructor
// prevents this.
// In nikic/PHP-Parser >= 2.0.0 and < 3.0.0 the default behavior was
// changed to not clone nodes, but the parameter was retained as
// an option.
// In nikic/PHP-Parser >= 3.0.0 the option to clone nodes was removed
// as a constructor parameter, so Scrutinizer will pick this up as
// an issue. It is retained for legacy compatibility.
$nodeTraverser = new NodeTraverser(false);
$variablesCollector = new StaticVariablesCollector($this);
$nodeTraverser->addVisitor($variablesCollector);
Expand Down Expand Up @@ -274,6 +283,15 @@ public function isDeprecated()
*/
public function isGenerator()
{
// In nikic/PHP-Parser < 2.0.0 the default behavior is cloning
// nodes when traversing them. Passing FALSE to the constructor
// prevents this.
// In nikic/PHP-Parser >= 2.0.0 and < 3.0.0 the default behavior was
// changed to not clone nodes, but the parameter was retained as
// an option.
// In nikic/PHP-Parser >= 3.0.0 the option to clone nodes was removed
// as a constructor parameter, so Scrutinizer will pick this up as
// an issue. It is retained for legacy compatibility.
$nodeTraverser = new NodeTraverser(false);
$nodeDetector = new GeneratorDetector();
$nodeTraverser->addVisitor($nodeDetector);
Expand Down
18 changes: 11 additions & 7 deletions src/ValueResolver/NodeExpressionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ protected function resolveScalarString(Scalar\String_ $node)
protected function resolveScalarMagicConstMethod()
{
if ($this->context instanceof \ReflectionMethod) {
$fullName = $this->context->getDeclaringClass()->getName() . '::' . $this->context->getShortName();
$fullName = $this->context->class . '::' . $this->context->getShortName();
Copy link

Choose a reason for hiding this comment

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

The suggestion in Scrutinizer CI inspection (see https://scrutinizer-ci.com/g/goaop/parser-reflection/inspections/eaf283f6-f982-44a4-bb28-51489bcba537/issues/files/src/ValueResolver/NodeExpressionResolver.php?status=fixed&orderField=path&order=asc&honorSelectedPaths=0) seems to be incorrect because mentioned https://bugs.php.net/bug.php?id=61384 PHP bug only affects getName method, but not getDeclaringClass method.

Knowing that I recommend

  • replacing $this->context->getDeclaringClass()->getName()
  • into $this->context->getDeclaringClass()->name instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

marking with 👍 to remind me to update.


return $fullName;
}
Expand Down Expand Up @@ -175,12 +175,12 @@ protected function resolveScalarMagicConstNamespace()
protected function resolveScalarMagicConstClass()
{
if ($this->context instanceof \ReflectionClass) {
return $this->context->getName();
return $this->context->name;
Copy link

Choose a reason for hiding this comment

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

Looks good.

}
if (method_exists($this->context, 'getDeclaringClass')) {
$declaringClass = $this->context->getDeclaringClass();
if ($declaringClass instanceof \ReflectionClass) {
return $declaringClass->getName();
return $declaringClass->name;
Copy link

Choose a reason for hiding this comment

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

Looks good.

}
}

Expand Down Expand Up @@ -213,7 +213,7 @@ protected function resolveScalarMagicConstLine(MagicConst\Line $node)
protected function resolveScalarMagicConstTrait()
{
if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) {
return $this->context->getName();
return $this->context->name;
Copy link

Choose a reason for hiding this comment

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

Looks good.

}

return '';
Expand All @@ -224,15 +224,14 @@ protected function resolveExprConstFetch(Expr\ConstFetch $node)
$constantValue = null;
$isResolved = false;

/** @var ReflectionFileNamespace|null $fileNamespace */
$fileNamespace = null;
$isFQNConstant = $node->name instanceof Node\Name\FullyQualified;
$constantName = $node->name->toString();

if (!$isFQNConstant) {
if (method_exists($this->context, 'getFileName')) {
$fileName = $this->context->getFileName();
$namespaceName = $this->resolveScalarMagicConstNamespace();
/** @var ReflectionFileNamespace $fileNamespace */
Copy link

Choose a reason for hiding this comment

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

You can safely remove this, because IDE can detect type from new ... construct by itself 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed... This comment just "came along for the ride" when I moved the variable's initialization.

$fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
if ($fileNamespace->hasConstant($constantName)) {
$constantValue = $fileNamespace->getConstant($constantName);
Expand All @@ -256,7 +255,12 @@ protected function resolveExprConstFetch(Expr\ConstFetch $node)

protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node)
{
$refClass = $this->fetchReflectionClass($node->class);
$classToReflect = $node->class;
Copy link

Choose a reason for hiding this comment

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

Doesn't seem to be related to Scrutinizer CI reported problems. Maybe belongs to separate PR with a description.

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'm not sure why this issue isn't showing up for you, nor why I can't find links to individual Scrutinizer issues, but this is to address the Scrutinizer issue reported for this line that reads:

Bug introduced 11 months ago by @lisachenko
It seems like $node->class can also be of type object<PhpParser\Node\Expr>; however, Go\ParserReflection\Valu...:fetchReflectionClass() does only seem to accept object<PhpParser\Node\Name>, maybe add an additional type check?

Copy link

Choose a reason for hiding this comment

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

Interesting. So class constant can be an expression. Seem to addition in PHP syntax, that PHP-Parser now supports. In either cases a test for that is needed too.

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 know you can do a constant fetch from a class name stored in a variable. I'm unsure what other Exprs are valid. I believe ParserReflection uses this mostly for evaluating constants, so this might not be a valid code path as we are using it, but it is a valid value for $node->class in some circumstances.

As far as adding corresponding test cases, I agree, but I was lumping my test changes into a separate PR. (I'm shooting for 100% code coverage, but I've always been a cockeyed optimist.) I can add these tests to this PR after I've untangled my current repo branches. (I don't really want to do a stash right now.)

Copy link

@aik099 aik099 Jul 26, 2017

Choose a reason for hiding this comment

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

Yes, I know you can do a constant fetch from a class name stored in a variable. I'm unsure what other Exprs are valid.

I've meant Example #4 Constant expression example from http://php.net/manual/en/language.oop5.constants.php page:

const ONE = 1;

class foo {
    // As of PHP 5.6.0
    const TWO = ONE * 2;
    const THREE = ONE + self::TWO;
    const SENTENCE = 'The value of THREE is '.self::THREE;
}

This way you can create test for that kind of syntax.

As far as adding corresponding test cases, I agree, but I was lumping my test changes into a separate PR.

The tests for changed code needs to come as part of PR, where that code is changed. Later you can send another PR for improving code coverage.

Copy link
Contributor Author

@loren-osborn loren-osborn Jul 27, 2017

Choose a reason for hiding this comment

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

I agree we need to test for this case, but it isn't demonstrated in the example above. It would be something like this, which I'm doubting is valid PHP:

const LEFT = 'Foo';
const RIGHT = 'Bar';

class FooBar {
    const FILENAME = 'baz.php';
}

require((LEFT . RIGHT)::FILENAME);

Like I said... I'm unsure if this is valid PHP, but I think, given that PhpParser accepts an expression, and we need something that can evaluate at compile time, something like this would be a valid test, even if not legal PHP currently.

Opinions?

Thumbing up to remind myself to add test cases. 👍

Copy link

Choose a reason for hiding this comment

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

Anything that would force PHP-Parser to create Expr class would do. Code won't be ever evaluated by PHP itself and only needs to be syntactically valid.

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 ($classToReflect instanceof Expr) {
$refClass = $this->resolve($classToReflect);
} else {
$refClass = $this->fetchReflectionClass($node->class);
}
$constantName = $node->name;

// special handling of ::class constants
Expand Down
9 changes: 4 additions & 5 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
use Go\ParserReflection\ReflectionEngine;

/**
* This file is used for automatic configuration of Go\ParserReflection\ReflectionEngine class
* This file is used for automatic configuration of
* Go\ParserReflection\ReflectionEngine class
*/
ReflectionEngine::init(new ComposerLocator());
// Polifyll for PHP<7.0
if (!class_exists(ReflectionType::class, false)) {
class ReflectionType {}
}

require(__DIR__ . '/polyfill.php');
Copy link

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm... I think this is a case of minor improvement in PSR-1 compliance for 1 extra small file load once, during execution. I think it's a worthwhile cost... and I think having Polyfills in a file called polyfill.php helps people know where to find them.

23 changes: 23 additions & 0 deletions src/polyfill.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Parser Reflection API
*
* @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/

/**
* This file is for ployfilling classes not defined in all supported
* versions of PHP, (i.e. PHP < 7).
*/
if (!class_exists(ReflectionType::class, false)) {
class ReflectionType

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

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 is obviously a non-issue for a polyfill class.

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

Choose a reason for hiding this comment

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

Each class must be in a namespace of at least one level (a top-level vendor name)

{
public function allowsNull()
{
return true;
}
}
}