Skip to content

Commit

Permalink
[Step 6 Upgrade] Add PHP 8+ Attributes support (#130)
Browse files Browse the repository at this point in the history
[Step 6 Upgrade] Add PHP 8+ Attributes support
  • Loading branch information
samsonasik authored Feb 4, 2024
1 parent b8e46fa commit bf823ac
Show file tree
Hide file tree
Showing 20 changed files with 553 additions and 1 deletion.
1 change: 1 addition & 0 deletions nitpick.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"ignore": [
"src/*",
"tests/*",
"docs/*"
]
Expand Down
109 changes: 109 additions & 0 deletions src/ReflectionAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);
/**
* 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.
*/

namespace Go\ParserReflection;

use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
use ReflectionAttribute as BaseReflectionAttribute;
use PhpParser\Node;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;

/**
* ref original usage https://3v4l.org/duaQI
*/
class ReflectionAttribute extends BaseReflectionAttribute
{
public function __construct(
private string $attributeName,
private ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant|ReflectionFunction|ReflectionParameter $reflector,
private array $arguments,
private bool $isRepeated,
) {
}

public function getNode(): Node\Attribute
{
/** @var Class_|ClassMethod|PropertyProperty|ClassConst|Function_|Param $node */
$node = $this->reflector->getNode();

// attrGroups only exists in Property Stmt
if ($node instanceof PropertyProperty) {
$node = $this->reflector->getTypeNode();
}

$nodeExpressionResolver = new NodeExpressionResolver($this);
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
if ($attr->name->toString() !== $this->attributeName) {
continue;
}

$arguments = [];
foreach ($attr->args as $arg) {
$nodeExpressionResolver->process($arg->value);
$arguments[] = $nodeExpressionResolver->getValue();
}

if ($arguments !== $this->arguments) {
continue;
}

return $attr;
}
}

throw new ReflectionException('ReflectionAttribute should be initiated from Go\ParserReflection Reflection classes');
}

public function isRepeated(): bool
{
return $this->isRepeated;
}

/**
* {@inheritDoc}
*/
public function getArguments(): array
{
return $this->arguments;
}

/**
* {@inheritDoc}
*/
public function getName(): string
{
return $this->attributeName;
}

/**
* {@inheritDoc}
*/
public function getTarget(): int
{
throw new \RuntimeException(sprintf('cannot get target from %s', $this::class));
}

/**
* {@inheritDoc}
*/
public function newInstance(): object
{
throw new \RuntimeException(sprintf('cannot create new instance from %s', $this::class));
}
}
2 changes: 2 additions & 0 deletions src/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Go\ParserReflection;

use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\Traits\ReflectionClassLikeTrait;
use PhpParser\Node\Name\FullyQualified;
Expand All @@ -27,6 +28,7 @@ class ReflectionClass extends InternalReflectionClass
{
use InternalPropertiesEmulationTrait;
use ReflectionClassLikeTrait;
use AttributeResolverTrait;

/**
* Initializes reflection instance
Expand Down
8 changes: 8 additions & 0 deletions src/ReflectionClassConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

namespace Go\ParserReflection;

use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
use PhpParser\Node;
use PhpParser\Node\Const_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassLike;
Expand All @@ -22,6 +24,7 @@
class ReflectionClassConstant extends BaseReflectionClassConstant
{
use InternalPropertiesEmulationTrait;
use AttributeResolverTrait;

/**
* Concrete class constant node
Expand Down Expand Up @@ -213,4 +216,9 @@ public function __toString(): string
(string) $value
);
}

public function getNode(): Node\Stmt\ClassConst
{
return $this->classConstantNode;
}
}
2 changes: 2 additions & 0 deletions src/ReflectionFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Go\ParserReflection;

use Closure;
use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait;
use PhpParser\Node\Stmt\Function_;
Expand All @@ -24,6 +25,7 @@ class ReflectionFunction extends BaseReflectionFunction
{
use InternalPropertiesEmulationTrait;
use ReflectionFunctionLikeTrait;
use AttributeResolverTrait;

/**
* Initializes reflection instance for given AST-node
Expand Down
2 changes: 2 additions & 0 deletions src/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Go\ParserReflection;

use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait;
use PhpParser\Node\Stmt\ClassLike;
Expand All @@ -25,6 +26,7 @@ class ReflectionMethod extends BaseReflectionMethod
{
use InternalPropertiesEmulationTrait;
use ReflectionFunctionLikeTrait;
use AttributeResolverTrait;

/**
* Name of the class
Expand Down
4 changes: 3 additions & 1 deletion src/ReflectionParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Go\ParserReflection;

use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
use PhpParser\Node\Expr;
Expand All @@ -32,6 +33,7 @@
class ReflectionParameter extends BaseReflectionParameter
{
use InternalPropertiesEmulationTrait;
use AttributeResolverTrait;

/**
* Reflection function or method
Expand Down Expand Up @@ -260,7 +262,7 @@ public function getDeclaringFunction(): ReflectionFunctionAbstract
/**
* {@inheritDoc}
*/
public function getDefaultValue()
public function getDefaultValue(): mixed
{
if (!$this->isDefaultValueAvailable()) {
throw new ReflectionException('Internal error: Failed to retrieve the default value');
Expand Down
2 changes: 2 additions & 0 deletions src/ReflectionProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Go\ParserReflection;

use Go\ParserReflection\Traits\AttributeResolverTrait;
use Go\ParserReflection\Traits\InitializationTrait;
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
Expand All @@ -27,6 +28,7 @@ class ReflectionProperty extends BaseReflectionProperty
{
use InitializationTrait;
use InternalPropertiesEmulationTrait;
use AttributeResolverTrait;

/**
* Type of property node
Expand Down
75 changes: 75 additions & 0 deletions src/Traits/AttributeResolverTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);
/**
* 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.
*/

namespace Go\ParserReflection\Traits;

use Go\ParserReflection\ReflectionAttribute;
use Go\ParserReflection\ReflectionProperty;
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;

trait AttributeResolverTrait
{
/**
* @return ReflectionAttribute[]
*/
public function getAttributes(?string $name = null, int $flags = 0): array
{
if ($this instanceof ReflectionProperty) {
$node = $this->getTypeNode();
} else {
$node = $this->getNode();
}

$node = $this->getNode();
$attributes = [];
$nodeExpressionResolver = new NodeExpressionResolver($this);

foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$arguments = [];
foreach ($attr->args as $arg) {
$nodeExpressionResolver->process($arg->value);
$arguments[] = $nodeExpressionResolver->getValue();
}

if ($name === null) {
$attributes[] = new ReflectionAttribute($attr->name->toString(), $this, $arguments, $this->isAttributeRepeated($attr->name->toString(), $node->attrGroups));

continue;
}

if ($name !== $attr->name->toString()) {
continue;
}

$attributes[] = new ReflectionAttribute($name, $this, $arguments, $this->isAttributeRepeated($name, $node->attrGroups));
}
}

return $attributes;
}

private function isAttributeRepeated(string $attributeName, array $attrGroups): bool
{
$count = 0;

foreach ($attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
if ($attr->name->toString() === $attributeName) {
++$count;
}
}
}

return $count >= 2;
}
}
7 changes: 7 additions & 0 deletions src/Traits/ReflectionClassLikeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,13 @@ public function getShortName(): string

public function getStartLine(): int
{
if ($this->classLikeNode->attrGroups !== []) {
$attrGroups = $this->classLikeNode->attrGroups;
$lastAttrGroupsEndLine = end($attrGroups)->getAttribute('endLine');

return $lastAttrGroupsEndLine + 1;
}

return $this->classLikeNode->getAttribute('startLine');
}

Expand Down
7 changes: 7 additions & 0 deletions src/Traits/ReflectionFunctionLikeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ public function getShortName(): string

public function getStartLine(): int
{
if ($this->functionLikeNode->attrGroups !== []) {
$attrGroups = $this->functionLikeNode->attrGroups;
$lastAttrGroupsEndLine = end($attrGroups)->getAttribute('endLine');

return $lastAttrGroupsEndLine + 1;
}

return $this->functionLikeNode->getAttribute('startLine');
}

Expand Down
9 changes: 9 additions & 0 deletions tests/Locator/ComposerLocatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use PHPUnit\Framework\TestCase;
use Go\ParserReflection\ReflectionClass;
use Go\ParserReflection\ReflectionEngine;

class ComposerLocatorTest extends TestCase
{
Expand All @@ -21,4 +22,12 @@ public function testLocateClass()
$locator->locateClass('\\' . ReflectionClass::class)
);
}

public function testLocateClassWithAttributes()
{
ReflectionEngine::init(new ComposerLocator());

$parsedClass = new \Go\ParserReflection\ReflectionClass(\Go\ParserReflection\Stub\RandomClassWithAttribute::class);
$this->assertIsArray($parsedClass->getAttributes());
}
}
Loading

0 comments on commit bf823ac

Please sign in to comment.