diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c822faa00..d4a49645a 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -34,6 +34,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index aea852550..2346c0e88 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -33,6 +33,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index e03be099a..5f48fbf4b 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -34,6 +34,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/demo-scripts.yml b/.github/workflows/demo-scripts.yml index 23725c3a9..521dc6d14 100644 --- a/.github/workflows/demo-scripts.yml +++ b/.github/workflows/demo-scripts.yml @@ -33,6 +33,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/mutation-tests.yml b/.github/workflows/mutation-tests.yml index bb97734c9..49250368f 100644 --- a/.github/workflows/mutation-tests.yml +++ b/.github/workflows/mutation-tests.yml @@ -33,6 +33,8 @@ jobs: coverage: "pcov" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1, zend.assertions=1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" @@ -64,6 +66,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Infection" run: "vendor/bin/roave-infection-static-analysis-plugin --coverage=coverage --threads=$(nproc)" diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 7cf85ce49..cbffd348d 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -35,6 +35,8 @@ jobs: php-version: "${{ matrix.php-version }}" # No deprecated errors ini-values: memory_limit=-1, error_reporting=24575 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index b06da28f9..84656bc29 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -37,6 +37,8 @@ jobs: coverage: "pcov" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 36d294278..25ad711f5 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -34,6 +34,8 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 + env: + update: true - name: "Cache dependencies" uses: "actions/cache@v2" diff --git a/composer.json b/composer.json index 7a7be008e..1f1b9dae8 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Better Reflection - an improved code reflection API", "license": "MIT", "require": { - "php": "~8.0.0 || ~8.1.0", + "php": "~8.0.12 || ~8.1.0", "ext-json": "*", "jetbrains/phpstorm-stubs": "2021.2", "nikic/php-parser": "^4.13.0", diff --git a/composer.lock b/composer.lock index 2ef23538b..f793e1d41 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e49fbb2bc61c4c8589ca31a33d9aed40", + "content-hash": "caa75a529f49ab01996c046db962c76d", "packages": [ { "name": "jetbrains/phpstorm-stubs", @@ -5151,7 +5151,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "~8.0.0 || ~8.1.0", + "php": "~8.0.12 || ~8.1.0", "ext-json": "*" }, "platform-dev": [], diff --git a/phpstan.neon b/phpstan.neon index 6e88cdc5f..a5378edc5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,6 +8,10 @@ parameters: bootstrapFiles: - stubs/ReflectionIntersectionType.php + # Needs new PHPStan version with newer stubs + excludePaths: + - src/Reflection/Adapter/ReflectionAttribute.php + ignoreErrors: - message: '#Access to an undefined property PhpParser\\Node\\Param::\$isOptional#' diff --git a/src/Reflection/Adapter/ReflectionAttribute.php b/src/Reflection/Adapter/ReflectionAttribute.php new file mode 100644 index 000000000..370605eef --- /dev/null +++ b/src/Reflection/Adapter/ReflectionAttribute.php @@ -0,0 +1,48 @@ +betterReflectionAttribute->getName(); + } + + public function getTarget(): int + { + return $this->betterReflectionAttribute->getTarget(); + } + + public function isRepeated(): bool + { + return $this->betterReflectionAttribute->isRepeated(); + } + + /** + * @return array + */ + public function getArguments(): array + { + return $this->betterReflectionAttribute->getArguments(); + } + + public function newInstance(): object + { + throw new Exception\NotImplemented('Not implemented'); + } + + public function __toString(): string + { + return $this->betterReflectionAttribute->__toString(); + } +} diff --git a/src/Reflection/Adapter/ReflectionClass.php b/src/Reflection/Adapter/ReflectionClass.php index 7109f1a5c..6806546e0 100644 --- a/src/Reflection/Adapter/ReflectionClass.php +++ b/src/Reflection/Adapter/ReflectionClass.php @@ -5,11 +5,11 @@ namespace Roave\BetterReflection\Reflection\Adapter; use OutOfBoundsException; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClass as CoreReflectionClass; use ReflectionException as CoreReflectionException; use ReflectionExtension as CoreReflectionExtension; use ReflectionMethod as CoreReflectionMethod; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; @@ -453,13 +453,23 @@ public function getShortName(): string } /** - * @return list + * @param class-string|null $name * - * @psalm-suppress LessSpecificImplementedReturnType + * @return list + * + * @psalm-suppress ImplementedReturnTypeMismatch */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionClass->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionClass->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionClass->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } public function isEnum(): bool diff --git a/src/Reflection/Adapter/ReflectionClassConstant.php b/src/Reflection/Adapter/ReflectionClassConstant.php index 451e8b984..604039487 100644 --- a/src/Reflection/Adapter/ReflectionClassConstant.php +++ b/src/Reflection/Adapter/ReflectionClassConstant.php @@ -4,10 +4,12 @@ namespace Roave\BetterReflection\Reflection\Adapter; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClassConstant as CoreReflectionClassConstant; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; +use function array_map; + final class ReflectionClassConstant extends CoreReflectionClassConstant { public function __construct(private BetterReflectionClassConstant $betterClassConstant) @@ -92,11 +94,21 @@ public function __toString(): string } /** - * @return list + * @param class-string|null $name + * + * @return list */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterClassConstant->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterClassConstant->getAttributesByName($name); + } else { + $attributes = $this->betterClassConstant->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } public function isFinal(): bool diff --git a/src/Reflection/Adapter/ReflectionFunction.php b/src/Reflection/Adapter/ReflectionFunction.php index a29c675d8..dc5b7da77 100644 --- a/src/Reflection/Adapter/ReflectionFunction.php +++ b/src/Reflection/Adapter/ReflectionFunction.php @@ -5,17 +5,18 @@ namespace Roave\BetterReflection\Reflection\Adapter; use Closure; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClass as CoreReflectionClass; use ReflectionException as CoreReflectionException; use ReflectionExtension as CoreReflectionExtension; use ReflectionFunction as CoreReflectionFunction; use ReflectionType as CoreReflectionType; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; use Roave\BetterReflection\Util\FileHelper; use Throwable; +use function array_map; use function func_get_args; final class ReflectionFunction extends CoreReflectionFunction @@ -204,9 +205,9 @@ public function getClosure(): Closure } /** - * @return list + * @return mixed[] */ - public function getAttributes(?string $name = null, int $flags = 0): array + public function getClosureUsedVariables(): array { throw new Exception\NotImplemented('Not implemented'); } @@ -221,16 +222,26 @@ public function getTentativeReturnType(): ?CoreReflectionType return ReflectionType::fromTypeOrNull($this->betterReflectionFunction->getTentativeReturnType()); } - /** - * @return mixed[] - */ - public function getClosureUsedVariables(): array + public function isStatic(): bool { - throw new Exception\NotImplemented('Not implemented'); + return $this->betterReflectionFunction->isStatic(); } - public function isStatic(): bool + /** + * @param class-string|null $name + * + * @return list + */ + public function getAttributes(?string $name = null, int $flags = 0): array { - return $this->betterReflectionFunction->isStatic(); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionFunction->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionFunction->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionFunction->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } } diff --git a/src/Reflection/Adapter/ReflectionMethod.php b/src/Reflection/Adapter/ReflectionMethod.php index c52152ad3..e9639dd35 100644 --- a/src/Reflection/Adapter/ReflectionMethod.php +++ b/src/Reflection/Adapter/ReflectionMethod.php @@ -5,7 +5,6 @@ namespace Roave\BetterReflection\Reflection\Adapter; use Closure; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClass as CoreReflectionClass; use ReflectionException as CoreReflectionException; use ReflectionExtension as CoreReflectionExtension; @@ -13,12 +12,14 @@ use ReflectionType as CoreReflectionType; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; use Roave\BetterReflection\Util\FileHelper; use Throwable; use TypeError; use ValueError; +use function array_map; use function func_get_args; final class ReflectionMethod extends CoreReflectionMethod @@ -290,11 +291,21 @@ private function isAccessible(): bool } /** - * @return list + * @param class-string|null $name + * + * @return list */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionMethod->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionMethod->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionMethod->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } public function hasTentativeReturnType(): bool diff --git a/src/Reflection/Adapter/ReflectionObject.php b/src/Reflection/Adapter/ReflectionObject.php index 7fb0e488e..5e89cb119 100644 --- a/src/Reflection/Adapter/ReflectionObject.php +++ b/src/Reflection/Adapter/ReflectionObject.php @@ -4,11 +4,11 @@ namespace Roave\BetterReflection\Reflection\Adapter; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClass as CoreReflectionClass; use ReflectionException as CoreReflectionException; use ReflectionExtension as CoreReflectionExtension; use ReflectionObject as CoreReflectionObject; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; @@ -454,13 +454,23 @@ public function isAnonymous(): bool } /** - * @return list + * @param class-string|null $name * - * @psalm-suppress LessSpecificImplementedReturnType + * @return list + * + * @psalm-suppress ImplementedReturnTypeMismatch */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionObject->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionObject->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionObject->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } public function isEnum(): bool diff --git a/src/Reflection/Adapter/ReflectionParameter.php b/src/Reflection/Adapter/ReflectionParameter.php index c3c85e4ea..17da5b862 100644 --- a/src/Reflection/Adapter/ReflectionParameter.php +++ b/src/Reflection/Adapter/ReflectionParameter.php @@ -4,13 +4,14 @@ namespace Roave\BetterReflection\Reflection\Adapter; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionClass as CoreReflectionClass; use ReflectionFunctionAbstract as CoreReflectionFunctionAbstract; use ReflectionParameter as CoreReflectionParameter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; use Roave\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; +use function array_map; use function assert; final class ReflectionParameter extends CoreReflectionParameter @@ -139,10 +140,20 @@ public function isPromoted(): bool } /** - * @return list + * @param class-string|null $name + * + * @return list */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionParameter->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionParameter->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionParameter->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } } diff --git a/src/Reflection/Adapter/ReflectionProperty.php b/src/Reflection/Adapter/ReflectionProperty.php index 6aef8d9ed..90a2c40cd 100644 --- a/src/Reflection/Adapter/ReflectionProperty.php +++ b/src/Reflection/Adapter/ReflectionProperty.php @@ -4,15 +4,17 @@ namespace Roave\BetterReflection\Reflection\Adapter; -use ReflectionAttribute as CoreReflectionAttribute; use ReflectionException as CoreReflectionException; use ReflectionProperty as CoreReflectionProperty; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\NotAnObject; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty; use Throwable; use TypeError; +use function array_map; + final class ReflectionProperty extends CoreReflectionProperty { private bool $accessible = false; @@ -156,11 +158,21 @@ public function isPromoted(): bool } /** - * @return list + * @param class-string|null $name + * + * @return list */ public function getAttributes(?string $name = null, int $flags = 0): array { - throw new Exception\NotImplemented('Not implemented'); + if ($name !== null && $flags & ReflectionAttribute::IS_INSTANCEOF) { + $attributes = $this->betterReflectionProperty->getAttributesByInstance($name); + } elseif ($name !== null) { + $attributes = $this->betterReflectionProperty->getAttributesByName($name); + } else { + $attributes = $this->betterReflectionProperty->getAttributes(); + } + + return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute => new ReflectionAttribute($betterReflectionAttribute), $attributes); } public function isReadOnly(): bool diff --git a/src/Reflection/Attribute/ReflectionAttributeHelper.php b/src/Reflection/Attribute/ReflectionAttributeHelper.php new file mode 100644 index 000000000..82beb1bdc --- /dev/null +++ b/src/Reflection/Attribute/ReflectionAttributeHelper.php @@ -0,0 +1,78 @@ + + */ + public static function createAttributes( + Reflector $reflector, + ReflectionClass|ReflectionMethod|ReflectionFunction|ReflectionClassConstant|ReflectionProperty|ReflectionParameter $reflection, + ) { + $repeated = []; + foreach ($reflection->getAst()->attrGroups as $attributesGroupNode) { + foreach ($attributesGroupNode->attrs as $attributeNode) { + $repeated[$attributeNode->name->toLowerString()][] = $attributeNode; + } + } + + $attributes = []; + foreach ($reflection->getAst()->attrGroups as $attributesGroupNode) { + foreach ($attributesGroupNode->attrs as $attributeNode) { + $attributes[] = new ReflectionAttribute( + $reflector, + $attributeNode, + $reflection, + count($repeated[$attributeNode->name->toLowerString()]) > 1, + ); + } + } + + return $attributes; + } + + /** + * @param list $attributes + * + * @return list + */ + public static function filterAttributesByName(array $attributes, string $name): array + { + return array_values(array_filter($attributes, static fn (ReflectionAttribute $attribute): bool => $attribute->getName() === $name)); + } + + /** + * @param list $attributes + * @param class-string $className + * + * @return list + */ + public static function filterAttributesByInstance(array $attributes, string $className): array + { + return array_values(array_filter($attributes, static function (ReflectionAttribute $attribute) use ($className): bool { + $class = $attribute->getClass(); + + return $class->getName() === $className || $class->isSubclassOf($className) || $class->implementsInterface($className); + })); + } +} diff --git a/src/Reflection/ReflectionAttribute.php b/src/Reflection/ReflectionAttribute.php new file mode 100644 index 000000000..b29c9579f --- /dev/null +++ b/src/Reflection/ReflectionAttribute.php @@ -0,0 +1,73 @@ +node->name->toString(); + } + + public function getClass(): ReflectionClass + { + return $this->reflector->reflectClass($this->getName()); + } + + /** + * @return array + */ + public function getArguments(): array + { + $arguments = []; + + $compiler = new CompileNodeToValue(); + $context = new CompilerContext($this->reflector, $this->owner); + + foreach ($this->node->args as $argNo => $arg) { + $argValue = $compiler->__invoke($arg->value, $context)->value; + $arguments[$arg->name?->toString() ?? $argNo] = $argValue; + } + + return $arguments; + } + + public function getTarget(): int + { + return match (true) { + $this->owner instanceof ReflectionClass => Attribute::TARGET_CLASS, + $this->owner instanceof ReflectionFunction => Attribute::TARGET_FUNCTION, + $this->owner instanceof ReflectionMethod => Attribute::TARGET_METHOD, + $this->owner instanceof ReflectionProperty => Attribute::TARGET_PROPERTY, + $this->owner instanceof ReflectionClassConstant => Attribute::TARGET_CLASS_CONSTANT, + $this->owner instanceof ReflectionParameter => Attribute::TARGET_PARAMETER, + }; + } + + public function isRepeated(): bool + { + return $this->isRepeated; + } + + public function __toString(): string + { + return ReflectionAttributeStringCast::toString($this); + } +} diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php index 84bec3083..00451f93d 100644 --- a/src/Reflection/ReflectionClass.php +++ b/src/Reflection/ReflectionClass.php @@ -21,6 +21,7 @@ use ReflectionProperty as CoreReflectionProperty; use Roave\BetterReflection\BetterReflection; use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper; +use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\NotAClassReflection; @@ -1477,6 +1478,32 @@ public function getDeclaringNamespaceAst(): ?Namespace_ return $this->declaringNamespace; } + /** + * @return list + */ + public function getAttributes(): array + { + return ReflectionAttributeHelper::createAttributes($this->reflector, $this); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); + } + /** * Set whether this class is final or not * diff --git a/src/Reflection/ReflectionClassConstant.php b/src/Reflection/ReflectionClassConstant.php index c89726437..dc21ee5b9 100644 --- a/src/Reflection/ReflectionClassConstant.php +++ b/src/Reflection/ReflectionClassConstant.php @@ -10,6 +10,7 @@ use Roave\BetterReflection\NodeCompiler\CompileNodeToValue; use Roave\BetterReflection\NodeCompiler\CompilerContext; use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper; +use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; use Roave\BetterReflection\Reflection\StringCast\ReflectionClassConstantStringCast; use Roave\BetterReflection\Reflector\Reflector; use Roave\BetterReflection\Util\CalculateReflectionColumn; @@ -186,4 +187,30 @@ public function getPositionInAst(): int { return $this->positionInNode; } + + /** + * @return list + */ + public function getAttributes(): array + { + return ReflectionAttributeHelper::createAttributes($this->reflector, $this); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); + } } diff --git a/src/Reflection/ReflectionFunctionAbstract.php b/src/Reflection/ReflectionFunctionAbstract.php index 93ba091c9..cf3c42ff7 100644 --- a/src/Reflection/ReflectionFunctionAbstract.php +++ b/src/Reflection/ReflectionFunctionAbstract.php @@ -22,6 +22,7 @@ use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper; +use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; use Roave\BetterReflection\Reflection\Exception\InvalidArrowFunctionBodyNode; use Roave\BetterReflection\Reflection\Exception\Uncloneable; use Roave\BetterReflection\Reflector\Reflector; @@ -466,6 +467,36 @@ public function getAst(): Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Cl return $this->node; } + /** + * @return list + */ + public function getAttributes(): array + { + /** + * @psalm-var ReflectionMethod|ReflectionFunction $this + * @phpstan-ignore-next-line + */ + return ReflectionAttributeHelper::createAttributes($this->reflector, $this); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); + } + /** * Override the method or function's body of statements with an entirely new * body of statements within the reflection. diff --git a/src/Reflection/ReflectionObject.php b/src/Reflection/ReflectionObject.php index ffa941233..0f984e8fd 100644 --- a/src/Reflection/ReflectionObject.php +++ b/src/Reflection/ReflectionObject.php @@ -483,6 +483,32 @@ public function getDeclaringNamespaceAst(): ?Namespace_ return $this->reflectionClass->getDeclaringNamespaceAst(); } + /** + * @return list + */ + public function getAttributes(): array + { + return $this->reflectionClass->getAttributes(); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return $this->reflectionClass->getAttributesByName($name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return $this->reflectionClass->getAttributesByInstance($className); + } + public function setFinal(bool $isFinal): void { $this->reflectionClass->setFinal($isFinal); diff --git a/src/Reflection/ReflectionParameter.php b/src/Reflection/ReflectionParameter.php index f67dd6de3..bdd3b3f2d 100644 --- a/src/Reflection/ReflectionParameter.php +++ b/src/Reflection/ReflectionParameter.php @@ -17,6 +17,7 @@ use Roave\BetterReflection\NodeCompiler\CompileNodeToValue; use Roave\BetterReflection\NodeCompiler\CompilerContext; use Roave\BetterReflection\NodeCompiler\Exception\UnableToCompileNode; +use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; use Roave\BetterReflection\Reflection\Exception\Uncloneable; use Roave\BetterReflection\Reflection\StringCast\ReflectionParameterStringCast; use Roave\BetterReflection\Reflector\Reflector; @@ -549,4 +550,30 @@ public function getAst(): ParamNode { return $this->node; } + + /** + * @return list + */ + public function getAttributes(): array + { + return ReflectionAttributeHelper::createAttributes($this->reflector, $this); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); + } } diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php index 1fc48729e..22c167f24 100644 --- a/src/Reflection/ReflectionProperty.php +++ b/src/Reflection/ReflectionProperty.php @@ -19,6 +19,7 @@ use Roave\BetterReflection\NodeCompiler\CompileNodeToValue; use Roave\BetterReflection\NodeCompiler\CompilerContext; use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper; +use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper; use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\NotAnObject; @@ -352,6 +353,32 @@ public function getPositionInAst(): int return $this->positionInNode; } + /** + * @return list + */ + public function getAttributes(): array + { + return ReflectionAttributeHelper::createAttributes($this->reflector, $this); + } + + /** + * @return list + */ + public function getAttributesByName(string $name): array + { + return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name); + } + + /** + * @param class-string $className + * + * @return list + */ + public function getAttributesByInstance(string $className): array + { + return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); + } + /** * {@inheritdoc} * diff --git a/src/Reflection/StringCast/ReflectionAttributeStringCast.php b/src/Reflection/StringCast/ReflectionAttributeStringCast.php new file mode 100644 index 000000000..f4cd08405 --- /dev/null +++ b/src/Reflection/StringCast/ReflectionAttributeStringCast.php @@ -0,0 +1,74 @@ +getArguments(); + + $argumentsFormat = $arguments !== [] ? " {\n - Arguments [%d] {%s\n }\n}" : ''; + + return sprintf( + 'Attribute [ %s ]' . $argumentsFormat . "\n", + $attributeReflection->getName(), + count($arguments), + self::argumentsToString($arguments), + ); + } + + /** + * @param array $arguments + */ + private static function argumentsToString(array $arguments): string + { + if ($arguments === []) { + return ''; + } + + $string = ''; + + $argumentNo = 0; + foreach ($arguments as $argumentName => $argumentValue) { + $string .= sprintf( + "\n Argument #%d [ %s%s ]", + $argumentNo, + is_string($argumentName) ? sprintf('%s = ', $argumentName) : '', + self::argumentValueToString($argumentValue), + ); + + $argumentNo++; + } + + return $string; + } + + private static function argumentValueToString(mixed $value): string + { + if (is_array($value)) { + return 'Array'; + } + + if (is_string($value) && strlen($value) > 15) { + return var_export(substr($value, 0, 15) . '...', true); + } + + return var_export($value, true); + } +} diff --git a/test/unit/Fixture/Attributes.php b/test/unit/Fixture/Attributes.php new file mode 100644 index 000000000..1076d6c14 --- /dev/null +++ b/test/unit/Fixture/Attributes.php @@ -0,0 +1,61 @@ + [$i], $methods)); + } + + /** + * @dataProvider coreReflectionMethodNamesProvider + */ + public function testCoreReflectionMethods(string $methodName): void + { + $reflectionTypeAdapterReflection = new CoreReflectionClass(ReflectionAttributeAdapter::class); + + self::assertTrue($reflectionTypeAdapterReflection->hasMethod($methodName)); + self::assertSame(ReflectionAttributeAdapter::class, $reflectionTypeAdapterReflection->getMethod($methodName)->getDeclaringClass()->getName()); + } + + public function methodExpectationProvider(): array + { + return [ + ['__toString', null, '', []], + ['getName', null, '', []], + ['getTarget', null, 1, []], + ['isRepeated', null, false, []], + ['getArguments', null, [], []], + ['newInstance', NotImplemented::class, null, []], + ]; + } + + /** + * @param mixed[] $args + * + * @dataProvider methodExpectationProvider + */ + public function testAdapterMethods(string $methodName, ?string $expectedException, mixed $returnValue, array $args): void + { + $reflectionStub = $this->createMock(BetterReflectionAttribute::class); + + if ($expectedException === null) { + $reflectionStub->expects($this->once()) + ->method($methodName) + ->with(...$args) + ->will($this->returnValue($returnValue)); + } + + if ($expectedException !== null) { + $this->expectException($expectedException); + } + + $adapter = new ReflectionAttributeAdapter($reflectionStub); + $adapter->{$methodName}(...$args); + } +} diff --git a/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php b/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php index 05dfe5977..2c055be02 100644 --- a/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php +++ b/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php @@ -8,7 +8,9 @@ use ReflectionClass as CoreReflectionClass; use ReflectionClassConstant as CoreReflectionClassConstant; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; @@ -51,7 +53,7 @@ public function methodExpectationProvider(): array ['getModifiers', null, 123, []], ['getDeclaringClass', null, $this->createMock(BetterReflectionClass::class), []], ['getDocComment', null, '', []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['isFinal', null, true, []], ['isEnumCase', NotImplemented::class, null, []], ]; @@ -92,4 +94,153 @@ public function testGetDocCommentReturnsFalseWhenNoDocComment(): void self::assertFalse($reflectionClassConstantAdapter->getDocComment()); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionClassConstant = $this->createMock(BetterReflectionClassConstant::class); + $betterReflectionClassConstant + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassConstantAdapter = new ReflectionClassConstantAdapter($betterReflectionClassConstant); + $attributes = $reflectionClassConstantAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionClassConstant = $this->getMockBuilder(BetterReflectionClassConstant::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClassConstant + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassAdapter = new ReflectionClassConstantAdapter($betterReflectionClassConstant); + $attributes = $reflectionClassAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionClassConstant = $this->getMockBuilder(BetterReflectionClassConstant::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClassConstant + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassConstantAdapter = new ReflectionClassConstantAdapter($betterReflectionClassConstant); + + self::assertCount(1, $reflectionClassConstantAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionClassConstantAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionClassConstantAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionClassTest.php b/test/unit/Reflection/Adapter/ReflectionClassTest.php index b0ac59b22..82b73dec1 100644 --- a/test/unit/Reflection/Adapter/ReflectionClassTest.php +++ b/test/unit/Reflection/Adapter/ReflectionClassTest.php @@ -11,8 +11,10 @@ use ReflectionMethod as CoreReflectionMethod; use ReflectionProperty as CoreReflectionProperty; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; @@ -105,7 +107,7 @@ public function methodExpectationProvider(): array ['inNamespace', null, true, []], ['getNamespaceName', null, '', []], ['getShortName', null, '', []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['isEnum', null, true, []], ]; } @@ -694,4 +696,153 @@ public function testUnknownProperty(): void $this->expectExceptionMessage('Property Roave\BetterReflection\Reflection\Adapter\ReflectionClass::$foo does not exist.'); $reflectionClassAdapter->foo; } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionClass = $this->createMock(BetterReflectionClass::class); + $betterReflectionClass + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass); + $attributes = $reflectionClassAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionClass = $this->getMockBuilder(BetterReflectionClass::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClass + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass); + $attributes = $reflectionClassAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionClass = $this->getMockBuilder(BetterReflectionClass::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClass + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass); + + self::assertCount(1, $reflectionClassAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionClassAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionClassAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionFunctionTest.php b/test/unit/Reflection/Adapter/ReflectionFunctionTest.php index 26993201a..3ca3543dd 100644 --- a/test/unit/Reflection/Adapter/ReflectionFunctionTest.php +++ b/test/unit/Reflection/Adapter/ReflectionFunctionTest.php @@ -10,7 +10,10 @@ use ReflectionException as CoreReflectionException; use ReflectionFunction as CoreReflectionFunction; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionFunction as ReflectionFunctionAdapter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; +use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; use Roave\BetterReflection\Reflection\ReflectionNamedType as BetterReflectionNamedType; use Roave\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; @@ -81,7 +84,7 @@ public function methodExpectationProvider(): array ['returnsReference', null, true, []], ['isGenerator', null, true, []], ['isVariadic', null, true, []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['hasTentativeReturnType', null, false, []], ['getTentativeReturnType', null, null, []], ['getClosureUsedVariables', NotImplemented::class, null, []], @@ -208,4 +211,153 @@ public function testInvokeArgsThrowsExceptionWhenError(): void $this->expectException(CoreReflectionException::class); $betterReflectionFunction->invokeArgs([]); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionFunction = $this->createMock(BetterReflectionFunction::class); + $betterReflectionFunction + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionFunctionAdapter = new ReflectionFunctionAdapter($betterReflectionFunction); + $attributes = $reflectionFunctionAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionFunction = $this->getMockBuilder(BetterReflectionFunction::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionFunction + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionFunctionAdapter = new ReflectionFunctionAdapter($betterReflectionFunction); + $attributes = $reflectionFunctionAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionFunction = $this->getMockBuilder(BetterReflectionFunction::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionFunction + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionFunctionAdapter = new ReflectionFunctionAdapter($betterReflectionFunction); + + self::assertCount(1, $reflectionFunctionAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionFunctionAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionFunctionAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionMethodTest.php b/test/unit/Reflection/Adapter/ReflectionMethodTest.php index b7cb8732a..a09479174 100644 --- a/test/unit/Reflection/Adapter/ReflectionMethodTest.php +++ b/test/unit/Reflection/Adapter/ReflectionMethodTest.php @@ -9,12 +9,14 @@ use ReflectionException as CoreReflectionException; use ReflectionMethod as CoreReflectionMethod; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionMethod as ReflectionMethodAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionNamedType as ReflectionNamedTypeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionParameter as ReflectionParameterAdapter; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; use Roave\BetterReflection\Reflection\ReflectionNamedType as BetterReflectionNamedType; @@ -89,7 +91,7 @@ public function methodExpectationProvider(): array ['returnsReference', null, true, []], ['isGenerator', null, true, []], ['isVariadic', null, true, []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['hasTentativeReturnType', null, false, []], ['getTentativeReturnType', null, null, []], ['getClosureUsedVariables', NotImplemented::class, null, []], @@ -349,4 +351,153 @@ public function testInvokeArgsThrowsExceptionWhenPropertyNotAccessible(): void $this->expectException(CoreReflectionException::class); $reflectionMethodAdapter->invokeArgs(); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionMethod = $this->createMock(BetterReflectionMethod::class); + $betterReflectionMethod + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod); + $attributes = $reflectionMethodAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionMethod = $this->getMockBuilder(BetterReflectionMethod::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionMethod + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod); + $attributes = $reflectionMethodAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionMethod = $this->getMockBuilder(BetterReflectionMethod::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionMethod + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod); + + self::assertCount(1, $reflectionMethodAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionMethodAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionMethodAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionObjectTest.php b/test/unit/Reflection/Adapter/ReflectionObjectTest.php index 73a8fb7bf..cc856c130 100644 --- a/test/unit/Reflection/Adapter/ReflectionObjectTest.php +++ b/test/unit/Reflection/Adapter/ReflectionObjectTest.php @@ -10,8 +10,10 @@ use ReflectionObject as CoreReflectionObject; use ReflectionProperty as CoreReflectionProperty; use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionObject as ReflectionObjectAdapter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; @@ -105,7 +107,7 @@ public function methodExpectationProvider(): array ['getNamespaceName', null, '', []], ['getShortName', null, '', []], ['isAnonymous', null, true, []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['isEnum', null, true, []], ]; } @@ -570,4 +572,168 @@ public function testGetReflectionConstantsReturnsClassConstantAdapter(): void self::assertContainsOnlyInstancesOf(ReflectionClassConstantAdapter::class, $reflectionObjectAdapter->getReflectionConstants()); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionObject = $this->createMock(BetterReflectionObject::class); + $betterReflectionObject + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject); + $attributes = $reflectionObjectAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionClass = $this->getMockBuilder(BetterReflectionClass::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClass + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $betterReflectionObjectReflection = new CoreReflectionClass(BetterReflectionObject::class); + $betterReflectionObject = $betterReflectionObjectReflection->newInstanceWithoutConstructor(); + + $betterReflectionObjectClassPropertyReflection = $betterReflectionObjectReflection->getProperty('reflectionClass'); + $betterReflectionObjectClassPropertyReflection->setAccessible(true); + $betterReflectionObjectClassPropertyReflection->setValue($betterReflectionObject, $betterReflectionClass); + + $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject); + + $attributes = $reflectionObjectAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionClass = $this->getMockBuilder(BetterReflectionClass::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionClass + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $betterReflectionObjectReflection = new CoreReflectionClass(BetterReflectionObject::class); + $betterReflectionObject = $betterReflectionObjectReflection->newInstanceWithoutConstructor(); + + $betterReflectionObjectClassPropertyReflection = $betterReflectionObjectReflection->getProperty('reflectionClass'); + $betterReflectionObjectClassPropertyReflection->setAccessible(true); + $betterReflectionObjectClassPropertyReflection->setValue($betterReflectionObject, $betterReflectionClass); + + $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject); + + self::assertCount(1, $reflectionObjectAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionObjectAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionObjectAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionParameterTest.php b/test/unit/Reflection/Adapter/ReflectionParameterTest.php index 9b019bff4..24c9f323b 100644 --- a/test/unit/Reflection/Adapter/ReflectionParameterTest.php +++ b/test/unit/Reflection/Adapter/ReflectionParameterTest.php @@ -7,8 +7,9 @@ use PHPUnit\Framework\TestCase; use ReflectionClass as CoreReflectionClass; use ReflectionParameter as CoreReflectionParameter; -use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionParameter as ReflectionParameterAdapter; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod; @@ -76,7 +77,7 @@ public function methodExpectationProvider(): array ['hasType', null, true, []], ['getType', null, $mockType, []], ['isPromoted', null, true, []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ]; } @@ -103,4 +104,153 @@ public function testAdapterMethods(string $methodName, ?string $expectedExceptio $adapter = new ReflectionParameterAdapter($reflectionStub); $adapter->{$methodName}(...$args); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionParameter = $this->createMock(BetterReflectionParameter::class); + $betterReflectionParameter + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionParameterAdapter = new ReflectionParameterAdapter($betterReflectionParameter); + $attributes = $reflectionParameterAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionParameter = $this->getMockBuilder(BetterReflectionParameter::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionParameter + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionParameterAdapter = new ReflectionParameterAdapter($betterReflectionParameter); + $attributes = $reflectionParameterAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionParameter = $this->getMockBuilder(BetterReflectionParameter::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionParameter + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionParameterAdapter = new ReflectionParameterAdapter($betterReflectionParameter); + + self::assertCount(1, $reflectionParameterAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionParameterAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionParameterAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php index 948c9a8de..39c145b0b 100644 --- a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php +++ b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php @@ -8,12 +8,13 @@ use ReflectionClass as CoreReflectionClass; use ReflectionException as CoreReflectionException; use ReflectionProperty as CoreReflectionProperty; -use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented; +use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter; use Roave\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter; use Roave\BetterReflection\Reflection\Exception\NoObjectProvided; use Roave\BetterReflection\Reflection\Exception\NotAnObject; use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass; +use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute; use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; use Roave\BetterReflection\Reflection\ReflectionNamedType as BetterReflectionNamedType; use Roave\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty; @@ -65,7 +66,7 @@ public function methodExpectationProvider(): array ['hasDefaultValue', null, true, []], ['getDefaultValue', null, null, []], ['isPromoted', null, true, []], - ['getAttributes', NotImplemented::class, null, []], + ['getAttributes', null, [], []], ['isReadOnly', null, true, []], ]; } @@ -306,4 +307,153 @@ public function testIsInitializedThrowsExceptionWhenObjectNotInstanceOfClass(): $reflectionPropertyAdapter->isInitialized(new stdClass()); } + + public function testGetAttributes(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class); + $betterReflectionProperty + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty); + $attributes = $reflectionPropertyAdapter->getAttributes(); + + self::assertCount(2, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + self::assertSame('AnotherAttribute', $attributes[1]->getName()); + } + + public function testGetAttributesWithName(): void + { + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getName') + ->willReturn('SomeAttribute'); + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getName') + ->willReturn('AnotherAttribute'); + + $betterReflectionAttributes = [$betterReflectionAttribute1, $betterReflectionAttribute2]; + + $betterReflectionProperty = $this->getMockBuilder(BetterReflectionProperty::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionProperty + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty); + $attributes = $reflectionPropertyAdapter->getAttributes('SomeAttribute'); + + self::assertCount(1, $attributes); + self::assertSame('SomeAttribute', $attributes[0]->getName()); + } + + public function testGetAttributesWithInstance(): void + { + $betterReflectionAttributeClass1 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $betterReflectionAttributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $betterReflectionAttribute1 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute1 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass1); + + $betterReflectionAttributeClass2 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute2 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute2 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass2); + + $betterReflectionAttributeClass3 = $this->createMock(BetterReflectionClass::class); + $betterReflectionAttributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $betterReflectionAttributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $betterReflectionAttributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $betterReflectionAttribute3 = $this->createMock(BetterReflectionAttribute::class); + $betterReflectionAttribute3 + ->method('getClass') + ->willReturn($betterReflectionAttributeClass3); + + $betterReflectionAttributes = [ + $betterReflectionAttribute1, + $betterReflectionAttribute2, + $betterReflectionAttribute3, + ]; + + $betterReflectionProperty = $this->getMockBuilder(BetterReflectionProperty::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributes']) + ->getMock(); + + $betterReflectionProperty + ->method('getAttributes') + ->willReturn($betterReflectionAttributes); + + $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty); + + self::assertCount(1, $reflectionPropertyAdapter->getAttributes('ClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionPropertyAdapter->getAttributes('ParentClassName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + self::assertCount(2, $reflectionPropertyAdapter->getAttributes('InterfaceName', ReflectionAttributeAdapter::IS_INSTANCEOF)); + } } diff --git a/test/unit/Reflection/Attribute/ReflectionAttributeHelperTest.php b/test/unit/Reflection/Attribute/ReflectionAttributeHelperTest.php new file mode 100644 index 000000000..4815549e7 --- /dev/null +++ b/test/unit/Reflection/Attribute/ReflectionAttributeHelperTest.php @@ -0,0 +1,151 @@ +createMock(Node\Stmt\Class_::class); + $ast->attrGroups = [ + new Node\AttributeGroup([ + new Node\Attribute(new Node\Name('SomeAttr')), + new Node\Attribute(new Node\Name('AnotherAttr')), + ]), + ]; + + $reflection = $this->createMock(ReflectionClass::class); + $reflection + ->method('getAst') + ->willReturn($ast); + + $attributes = ReflectionAttributeHelper::createAttributes( + $this->createMock(Reflector::class), + $reflection, + ); + + self::assertCount(2, $attributes); + } + + public function testFilterAttributesByName(): void + { + $attribute1 = $this->createMock(ReflectionAttribute::class); + $attribute1 + ->method('getName') + ->willReturn('SomeAttr'); + + $attribute2 = $this->createMock(ReflectionAttribute::class); + $attribute2 + ->method('getName') + ->willReturn('AnotherAttr'); + + $attribute3 = $this->createMock(ReflectionAttribute::class); + $attribute3 + ->method('getName') + ->willReturn('AnotherAttr'); + + $attributes = [ + $attribute1, + $attribute2, + $attribute3, + ]; + + self::assertCount(1, ReflectionAttributeHelper::filterAttributesByName($attributes, 'SomeAttr')); + self::assertCount(2, ReflectionAttributeHelper::filterAttributesByName($attributes, 'AnotherAttr')); + } + + public function testFilterAttributesByInstance(): void + { + $attributeClass1 = $this->createMock(ReflectionClass::class); + $attributeClass1 + ->method('getName') + ->willReturn('ClassName'); + $attributeClass1 + ->method('isSubclassOf') + ->willReturnMap([ + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $attributeClass1 + ->method('implementsInterface') + ->willReturnMap([ + ['ParentClassName', false], + ['InterfaceName', false], + ]); + + $attribute1 = $this->createMock(ReflectionAttribute::class); + $attribute1 + ->method('getClass') + ->willReturn($attributeClass1); + + $attributeClass2 = $this->createMock(ReflectionClass::class); + $attributeClass2 + ->method('getName') + ->willReturn('Whatever'); + $attributeClass2 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', false], + ]); + $attributeClass2 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $attribute2 = $this->createMock(ReflectionAttribute::class); + $attribute2 + ->method('getClass') + ->willReturn($attributeClass2); + + $attributeClass3 = $this->createMock(ReflectionClass::class); + $attributeClass3 + ->method('getName') + ->willReturn('Whatever'); + $attributeClass3 + ->method('isSubclassOf') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', true], + ['InterfaceName', false], + ]); + $attributeClass3 + ->method('implementsInterface') + ->willReturnMap([ + ['ClassName', false], + ['ParentClassName', false], + ['InterfaceName', true], + ]); + + $attribute3 = $this->createMock(ReflectionAttribute::class); + $attribute3 + ->method('getClass') + ->willReturn($attributeClass3); + + $attributes = [ + $attribute1, + $attribute2, + $attribute3, + ]; + + self::assertCount(1, ReflectionAttributeHelper::filterAttributesByInstance($attributes, 'ClassName')); + self::assertCount(2, ReflectionAttributeHelper::filterAttributesByInstance($attributes, 'ParentClassName')); + self::assertCount(2, ReflectionAttributeHelper::filterAttributesByInstance($attributes, 'InterfaceName')); + } +} diff --git a/test/unit/Reflection/ReflectionAttributeTest.php b/test/unit/Reflection/ReflectionAttributeTest.php new file mode 100644 index 000000000..f3f82cafe --- /dev/null +++ b/test/unit/Reflection/ReflectionAttributeTest.php @@ -0,0 +1,176 @@ +astLocator = BetterReflectionSingleton::instance()->astLocator(); + $this->reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + } + + public function testAttributes(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testRepeatedAttributes(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithRepeatedAttributes::class); + + $notRepeatedAttributes = $classReflection->getAttributesByName(Attr::class); + self::assertCount(1, $notRepeatedAttributes); + self::assertFalse($notRepeatedAttributes[0]->isRepeated()); + + $repeatedAttributes = $classReflection->getAttributesByName(AnotherAttr::class); + self::assertCount(2, $repeatedAttributes); + self::assertTrue($repeatedAttributes[0]->isRepeated()); + } + + public function testGetName(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributes(); + + self::assertCount(2, $attributes); + + self::assertSame(Attr::class, $attributes[0]->getName()); + self::assertSame(AnotherAttr::class, $attributes[1]->getName()); + } + + public function testGetClass(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributes(); + + self::assertCount(2, $attributes); + + $firstAttributeClass = $attributes[0]->getClass(); + self::assertInstanceOf(ReflectionClass::class, $firstAttributeClass); + self::assertSame(Attr::class, $firstAttributeClass->getName()); + + $secondAttributeClass = $attributes[1]->getClass(); + self::assertInstanceOf(ReflectionClass::class, $secondAttributeClass); + self::assertSame(AnotherAttr::class, $secondAttributeClass->getName()); + } + + public function testGetArgumentsWhenNoArguments(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + self::assertCount(0, $attributes[0]->getArguments()); + } + + public function testGetArgumentsWithArguments(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributesWithArguments::class); + $attributes = $classReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + + $expectedArguments = [ + 0 => 'arg1', + 1 => 'arg2', + 'arg3' => ClassWithAttributesWithArguments::class, + 'arg4' => [ + 0 => 0, + 1 => ClassWithAttributes::class, + 2 => [ + ClassWithAttributesWithArguments::class, + ClassWithRepeatedAttributes::class, + ], + ], + ]; + + self::assertSame($expectedArguments, $attributes[0]->getArguments()); + } + + public function testGetTargetWithClass(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_CLASS, $attributes[0]->getTarget()); + } + + public function testGetTargetWithClassConstant(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $constantReflection = $classReflection->getReflectionConstant('CONSTANT_WITH_ATTRIBUTES'); + $attributes = $constantReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_CLASS_CONSTANT, $attributes[0]->getTarget()); + } + + public function testGetTargetWithProperty(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $propertyReflection = $classReflection->getProperty('propertyWithAttributes'); + $attributes = $propertyReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_PROPERTY, $attributes[0]->getTarget()); + } + + public function testGetTargetWithMethod(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $attributes = $methodReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_METHOD, $attributes[0]->getTarget()); + } + + public function testGetTargetWithParameter(): void + { + $classReflection = $this->reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $parameterReflection = $methodReflection->getParameter('parameterWithAttributes'); + $attributes = $parameterReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_PARAMETER, $attributes[0]->getTarget()); + } + + public function testGetTargetWithFunction(): void + { + $functionReflection = $this->reflector->reflectFunction('Roave\BetterReflectionTest\Fixture\functionWithAttributes'); + $attributes = $functionReflection->getAttributes(); + + self::assertNotEmpty($attributes); + self::assertSame(Attribute::TARGET_FUNCTION, $attributes[0]->getTarget()); + } +} diff --git a/test/unit/Reflection/ReflectionClassConstantTest.php b/test/unit/Reflection/ReflectionClassConstantTest.php index a9c29ac4a..b9919b070 100644 --- a/test/unit/Reflection/ReflectionClassConstantTest.php +++ b/test/unit/Reflection/ReflectionClassConstantTest.php @@ -9,20 +9,33 @@ use ReflectionClassConstant as CoreReflectionClassConstant; use Roave\BetterReflection\Reflection\ReflectionClassConstant; use Roave\BetterReflection\Reflector\DefaultReflector; +use Roave\BetterReflection\SourceLocator\Ast\Locator; use Roave\BetterReflection\SourceLocator\Type\ComposerSourceLocator; +use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; +use Roave\BetterReflectionTest\Fixture\Attr; +use Roave\BetterReflectionTest\Fixture\ClassWithAttributes; use Roave\BetterReflectionTest\Fixture\ExampleClass; use function sprintf; class ReflectionClassConstantTest extends TestCase { + private Locator $astLocator; + + public function setUp(): void + { + parent::setUp(); + + $this->astLocator = BetterReflectionSingleton::instance()->astLocator(); + } + private function getComposerLocator(): ComposerSourceLocator { return new ComposerSourceLocator( require __DIR__ . '/../../../vendor/autoload.php', - BetterReflectionSingleton::instance()->astLocator(), + $this->astLocator, ); } @@ -114,7 +127,7 @@ public function testGetDeclaringClass(): void */ public function testStartEndLine(string $php, int $startLine, int $endLine): void { - $reflector = new DefaultReflector(new StringSourceLocator($php, BetterReflectionSingleton::instance()->astLocator())); + $reflector = new DefaultReflector(new StringSourceLocator($php, $this->astLocator)); $classReflection = $reflector->reflectClass('\T'); $constReflection = $classReflection->getReflectionConstant('TEST'); self::assertEquals($startLine, $constReflection->getStartLine()); @@ -145,7 +158,7 @@ public function columnsProvider(): array */ public function testGetStartColumnAndEndColumn(string $php, int $startColumn, int $endColumn): void { - $reflector = new DefaultReflector(new StringSourceLocator($php, BetterReflectionSingleton::instance()->astLocator())); + $reflector = new DefaultReflector(new StringSourceLocator($php, $this->astLocator)); $classReflection = $reflector->reflectClass('T'); $constantReflection = $classReflection->getReflectionConstant('TEST'); @@ -175,7 +188,7 @@ class Foo } PHP; - $reflector = new DefaultReflector(new StringSourceLocator($php, BetterReflectionSingleton::instance()->astLocator())); + $reflector = new DefaultReflector(new StringSourceLocator($php, $this->astLocator)); $classReflection = $reflector->reflectClass('Foo'); $constantReflection = $classReflection->getReflectionConstant($constantName); @@ -225,4 +238,44 @@ class Foo { self::assertSame($isDeprecated, $constantReflection->isDeprecated()); } + + public function testGetAttributesWithoutAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ExampleClass::class); + $constantReflection = $classReflection->getReflectionConstant('MY_CONST_1'); + $attributes = $constantReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $constantReflection = $classReflection->getReflectionConstant('CONSTANT_WITH_ATTRIBUTES'); + $attributes = $constantReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $constantReflection = $classReflection->getReflectionConstant('CONSTANT_WITH_ATTRIBUTES'); + $attributes = $constantReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $constantReflection = $classReflection->getReflectionConstant('CONSTANT_WITH_ATTRIBUTES'); + $attributes = $constantReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index 151ab6a48..5f07d168b 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -43,11 +43,13 @@ use Roave\BetterReflectionTest\ClassWithInterfacesOther; use Roave\BetterReflectionTest\Fixture; use Roave\BetterReflectionTest\Fixture\AbstractClass; +use Roave\BetterReflectionTest\Fixture\Attr; use Roave\BetterReflectionTest\Fixture\ClassExtendingNonAbstractClass; use Roave\BetterReflectionTest\Fixture\ClassForHinting; use Roave\BetterReflectionTest\Fixture\ClassUsesAndRenamesMethodFromTrait; use Roave\BetterReflectionTest\Fixture\ClassUsesTwoTraitsWithSameMethodNameOneIsAbstract; use Roave\BetterReflectionTest\Fixture\ClassUsingTraitWithAbstractMethod; +use Roave\BetterReflectionTest\Fixture\ClassWithAttributes; use Roave\BetterReflectionTest\Fixture\ClassWithCaseInsensitiveMethods; use Roave\BetterReflectionTest\Fixture\ClassWithMissingParent; use Roave\BetterReflectionTest\Fixture\ExampleClass; @@ -2152,4 +2154,40 @@ class IsNotEnum $isNotEnum = $reflector->reflectClass('IsNotEnum'); self::assertFalse($isNotEnum->isEnum()); } + + public function testGetAttributesWithoutAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ExampleClass::class); + $attributes = $classReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $attributes = $classReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/ReflectionFunctionTest.php b/test/unit/Reflection/ReflectionFunctionTest.php index df48f98a2..86f12e19f 100644 --- a/test/unit/Reflection/ReflectionFunctionTest.php +++ b/test/unit/Reflection/ReflectionFunctionTest.php @@ -15,8 +15,10 @@ use Roave\BetterReflection\SourceLocator\Ast\Locator; use Roave\BetterReflection\SourceLocator\SourceStubber\SourceStubber; use Roave\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; +use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; +use Roave\BetterReflectionTest\Fixture\Attr; use Roave\BetterReflectionTest\Fixture\ClassWithStaticMethod; use stdClass; @@ -345,4 +347,42 @@ public function testInvokeArgsThrowsExceptionWhenFunctionDoesNotExist(): void $functionReflection->invokeArgs(); } + + public function testGetAttributesWithoutAttributes(): void + { + $php = 'astLocator)); + $functionReflection = $reflector->reflectFunction('foo'); + $attributes = $functionReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $functionReflection = $reflector->reflectFunction('Roave\BetterReflectionTest\Fixture\functionWithAttributes'); + $attributes = $functionReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $functionReflection = $reflector->reflectFunction('Roave\BetterReflectionTest\Fixture\functionWithAttributes'); + $attributes = $functionReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $functionReflection = $reflector->reflectFunction('Roave\BetterReflectionTest\Fixture\functionWithAttributes'); + $attributes = $functionReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/ReflectionMethodTest.php b/test/unit/Reflection/ReflectionMethodTest.php index 7216f4e18..713a80b87 100644 --- a/test/unit/Reflection/ReflectionMethodTest.php +++ b/test/unit/Reflection/ReflectionMethodTest.php @@ -29,7 +29,9 @@ use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; +use Roave\BetterReflectionTest\Fixture\Attr; use Roave\BetterReflectionTest\Fixture\ClassUsesTraitWithStaticMethod; +use Roave\BetterReflectionTest\Fixture\ClassWithAttributes; use Roave\BetterReflectionTest\Fixture\ClassWithNonStaticMethod; use Roave\BetterReflectionTest\Fixture\ClassWithStaticMethod; use Roave\BetterReflectionTest\Fixture\ExampleClass; @@ -606,4 +608,44 @@ public function testInterfaceMethodBodyAst(): void self::assertSame([], $methodInfo->getBodyAst()); } + + public function testGetAttributesWithoutAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ExampleClass::class); + $methodReflection = $classReflection->getMethod('__construct'); + $attributes = $methodReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $attributes = $methodReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $attributes = $methodReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $attributes = $methodReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/ReflectionParameterTest.php b/test/unit/Reflection/ReflectionParameterTest.php index 1b83c8366..1e3defd80 100644 --- a/test/unit/Reflection/ReflectionParameterTest.php +++ b/test/unit/Reflection/ReflectionParameterTest.php @@ -23,8 +23,11 @@ use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; +use Roave\BetterReflectionTest\Fixture\Attr; use Roave\BetterReflectionTest\Fixture\ClassForHinting; +use Roave\BetterReflectionTest\Fixture\ClassWithAttributes; use Roave\BetterReflectionTest\Fixture\ClassWithConstantsAsDefaultValues; +use Roave\BetterReflectionTest\Fixture\ExampleClass; use Roave\BetterReflectionTest\Fixture\Methods; use Roave\BetterReflectionTest\Fixture\NullableParameterTypeDeclarations; use Roave\BetterReflectionTest\Fixture\PhpParameterTypeDeclarations; @@ -768,4 +771,47 @@ public function testGetAst(): void self::assertInstanceOf(Param::class, $ast); self::assertSame('boo', $ast->var->name); } + + public function testGetAttributesWithoutAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ExampleClass::class); + $methodReflection = $classReflection->getMethod('__construct'); + $attributes = $methodReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $parameterReflection = $methodReflection->getParameter('parameterWithAttributes'); + $attributes = $parameterReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $parameterReflection = $methodReflection->getParameter('parameterWithAttributes'); + $attributes = $parameterReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $methodReflection = $classReflection->getMethod('methodWithAttributes'); + $parameterReflection = $methodReflection->getParameter('parameterWithAttributes'); + $attributes = $parameterReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/ReflectionPropertyTest.php b/test/unit/Reflection/ReflectionPropertyTest.php index 31e484942..a1cc17dee 100644 --- a/test/unit/Reflection/ReflectionPropertyTest.php +++ b/test/unit/Reflection/ReflectionPropertyTest.php @@ -28,8 +28,10 @@ use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; +use Roave\BetterReflectionTest\Fixture\Attr; use Roave\BetterReflectionTest\Fixture\ClassForHinting; use Roave\BetterReflectionTest\Fixture\ClassUsesTraitStaticPropertyGetSet; +use Roave\BetterReflectionTest\Fixture\ClassWithAttributes; use Roave\BetterReflectionTest\Fixture\ExampleClass; use Roave\BetterReflectionTest\Fixture\InitializedProperties; use Roave\BetterReflectionTest\Fixture\Php74PropertyTypeDeclarations; @@ -834,4 +836,44 @@ class Foo { self::assertSame($isDeprecated, $propertyReflection->isDeprecated()); } + + public function testGetAttributesWithoutAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ExampleClass::class); + $propertyReflection = $classReflection->getProperty('privateProperty'); + $attributes = $propertyReflection->getAttributes(); + + self::assertCount(0, $attributes); + } + + public function testGetAttributesWithAttributes(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $propertyReflection = $classReflection->getProperty('propertyWithAttributes'); + $attributes = $propertyReflection->getAttributes(); + + self::assertCount(2, $attributes); + } + + public function testGetAttributesByName(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $propertyReflection = $classReflection->getProperty('propertyWithAttributes'); + $attributes = $propertyReflection->getAttributesByName(Attr::class); + + self::assertCount(1, $attributes); + } + + public function testGetAttributesByInstance(): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/Attributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributes::class); + $propertyReflection = $classReflection->getProperty('propertyWithAttributes'); + $attributes = $propertyReflection->getAttributesByInstance(Attr::class); + + self::assertCount(2, $attributes); + } } diff --git a/test/unit/Reflection/StringCast/ReflectionAttributeStringCastTest.php b/test/unit/Reflection/StringCast/ReflectionAttributeStringCastTest.php new file mode 100644 index 000000000..624646153 --- /dev/null +++ b/test/unit/Reflection/StringCast/ReflectionAttributeStringCastTest.php @@ -0,0 +1,49 @@ +astLocator = $betterReflection->astLocator(); + } + + public function toStringProvider(): array + { + return [ + ['Roave\BetterReflectionTest\Fixture\NoArguments', "Attribute [ Roave\BetterReflectionTest\Fixture\NoArguments ]\n"], + ['Roave\BetterReflectionTest\Fixture\WithArguments', "Attribute [ Roave\BetterReflectionTest\Fixture\WithArguments ] {\n - Arguments [4] {\n Argument #0 [ 'arg1' ]\n Argument #1 [ 'very long strin...' ]\n Argument #2 [ arg3 = Array ]\n Argument #3 [ arg4 = true ]\n }\n}\n"], + ]; + } + + /** + * @dataProvider toStringProvider + */ + public function testToString(string $attributeName, string $expectedString): void + { + $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../../Fixture/StringCastAttributes.php', $this->astLocator)); + $classReflection = $reflector->reflectClass(ClassWithAttributesForStringCast::class); + $attributeReflection = $classReflection->getAttributesByName($attributeName)[0]; + + self::assertSame($expectedString, (string) $attributeReflection); + } +}