Skip to content
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
49 changes: 46 additions & 3 deletions src/ReflectionAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ReflectionAttribute extends BaseReflectionAttribute implements NodeAwareIn

/**
* @param class-string<object> $attributeName
* @param array<int, mixed> $arguments
* @param array<array-key, mixed> $arguments
*/
public function __construct(
string $attributeName,
Expand Down Expand Up @@ -83,7 +83,12 @@ public function getNode(): Node\Attribute
$arguments = [];
foreach ($attr->args as $arg) {
$nodeExpressionResolver->process($arg->value);
$arguments[] = $nodeExpressionResolver->getValue();
// Named arguments are keyed by their name, exactly like the native reflection does
if (isset($arg->name)) {
$arguments[$arg->name->toString()] = $nodeExpressionResolver->getValue();
} else {
$arguments[] = $nodeExpressionResolver->getValue();
}
}

if ($arguments !== $this->arguments) {
Expand All @@ -105,7 +110,7 @@ public function isRepeated(): bool
/**
* {@inheritDoc}
*
* @return array<int, mixed>
* @return array<array-key, mixed>
*/
public function getArguments(): array
{
Expand All @@ -120,6 +125,44 @@ public function getName(): string
return $this->attributeName;
}

/**
* Returns the attribute class name without the namespace prefix.
*
* Available in the native reflection since PHP 8.6, but the internal implementation operates on
* the internal attribute structure that is never initialized for the parsed reflection, therefore
* it is always resolved from the attribute name itself.
*/
public function getShortName(): string
{
$nameParts = explode('\\', $this->getName());

return (string) array_pop($nameParts);
}

/**
* Returns the namespace of the attribute class or an empty string for the global namespace.
*
* @see self::getShortName() for the reason why this method is not inherited from the native one
*/
public function getNamespaceName(): string
{
$nameParts = explode('\\', $this->getName());
// Removes the last part with the short class name itself
array_pop($nameParts);

return implode('\\', $nameParts);
}

/**
* Checks if the attribute class is defined in a namespace.
*
* @see self::getShortName() for the reason why this method is not inherited from the native one
*/
public function inNamespace(): bool
{
return $this->getNamespaceName() !== '';
}

/**
* {@inheritDoc}
*/
Expand Down
6 changes: 5 additions & 1 deletion src/ReflectionClassConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,11 @@ public function __toString(): string
$valueType = $this->type ?? new ReflectionType('mixed', false);
}

return sprintf(
// Native reflection prints the doc comment of the constant right before its declaration
$docComment = $this->getDocComment();
$prefix = $docComment !== false ? $docComment . "\n" : '';

return $prefix . sprintf(
"Constant [ %s %s %s ] { %s }\n",
implode(' ', Reflection::getModifierNames($this->getModifiers())),
ReflectionType::convertToDisplayType($valueType),
Expand Down
23 changes: 19 additions & 4 deletions src/ReflectionConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ public function getNamespaceName(): string
return implode('\\', $namespaceParts);
}

/**
* Checks if the constant is defined in a namespace
*
* Mirrors the \ReflectionConstant::inNamespace() method that was added in PHP 8.6
*/
public function inNamespace(): bool
{
return $this->getNamespaceName() !== '';
}

/**
* Returns the value of the constant, evaluated at the pure AST level
*/
Expand Down Expand Up @@ -179,7 +189,12 @@ public function getAttributes(?string $name = null, int $flags = 0): array
$arguments = [];
foreach ($attr->args as $arg) {
$nodeExpressionResolver->process($arg->value);
$arguments[] = $nodeExpressionResolver->getValue();
// Named arguments are keyed by their name, exactly like the native reflection does
if (isset($arg->name)) {
$arguments[$arg->name->toString()] = $nodeExpressionResolver->getValue();
} else {
$arguments[] = $nodeExpressionResolver->getValue();
}
}

$isRepeated = self::isAttributeRepeated($resolvedAttrName, $this->declarationNode->attrGroups);
Expand Down Expand Up @@ -292,7 +307,7 @@ private static function isAttributeRepeated(string $attributeName, array $attrGr
* argument of ReflectionAttribute does not accept a constant reflection.
*
* @param class-string<object> $attributeName
* @param array<int, mixed> $arguments
* @param array<array-key, mixed> $arguments
*/
private static function createAttributeReflection(
Attribute $attributeNode,
Expand All @@ -303,7 +318,7 @@ private static function createAttributeReflection(
return new class ($attributeNode, $attributeName, $arguments, $isRepeated) extends ReflectionAttribute {
/**
* @param class-string<object> $attributeClassName
* @param array<int, mixed> $attributeArguments
* @param array<array-key, mixed> $attributeArguments
*/
public function __construct(
private Attribute $attributeNode,
Expand All @@ -323,7 +338,7 @@ public function getName(): string
}

/**
* @return array<int, mixed>
* @return array<array-key, mixed>
*/
public function getArguments(): array
{
Expand Down
7 changes: 6 additions & 1 deletion src/Traits/AttributeResolverTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ public function getAttributes(?string $name = null, int $flags = 0): array
$arguments = [];
foreach ($attr->args as $arg) {
$nodeExpressionResolver->process($arg->value);
$arguments[] = $nodeExpressionResolver->getValue();
// Named arguments are keyed by their name, exactly like the native reflection does
if (isset($arg->name)) {
$arguments[$arg->name->toString()] = $nodeExpressionResolver->getValue();
} else {
$arguments[] = $nodeExpressionResolver->getValue();
}
}

$attributeNameNode = $attr->name;
Expand Down
Loading