diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 5ab7a9b38..d7ecd58b0 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -24,6 +24,7 @@ jobs:
php-version:
- "8.2"
- "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
- "windows-latest"
@@ -67,6 +68,7 @@ jobs:
php-version:
- "8.2"
- "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
@@ -107,6 +109,7 @@ jobs:
dependencies:
- "locked"
php-version:
+ # Throws deprecated errors on 8.4
- "8.3"
operating-system:
- "ubuntu-latest"
@@ -148,6 +151,7 @@ jobs:
dependencies:
- "locked"
php-version:
+ # Does not work on 8.4
- "8.3"
operating-system:
- "ubuntu-latest"
@@ -192,7 +196,7 @@ jobs:
dependencies:
- "locked"
php-version:
- - "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
- "windows-latest"
@@ -234,7 +238,7 @@ jobs:
dependencies:
- "locked"
php-version:
- - "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
@@ -268,7 +272,7 @@ jobs:
dependencies:
- "locked"
php-version:
- - "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
- "windows-latest"
@@ -347,7 +351,7 @@ jobs:
dependencies:
- "locked"
php-version:
- - "8.3"
+ - "8.4"
operating-system:
- "ubuntu-latest"
diff --git a/composer.json b/composer.json
index cec0cb8d0..7e4f742aa 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"description": "Better Reflection - an improved code reflection API",
"license": "MIT",
"require": {
- "php": "~8.2.0 || ~8.3.2",
+ "php": "~8.2.0 || ~8.3.2 || ~8.4.1",
"ext-json": "*",
"jetbrains/phpstorm-stubs": "2024.2",
"nikic/php-parser": "^5.3.1"
diff --git a/composer.lock b/composer.lock
index 8d3a66ae3..e2c064551 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": "51f69226dac86a1c51783cb2205bf72e",
+ "content-hash": "b4ed745f3892bdbce0cbfeb5e100340a",
"packages": [
{
"name": "jetbrains/phpstorm-stubs",
@@ -3220,7 +3220,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
- "php": "~8.2.0 || ~8.3.2",
+ "php": "~8.2.0 || ~8.3.2 || ~8.4.1",
"ext-json": "*"
},
"platform-dev": {},
diff --git a/phpstan.neon b/phpstan.neon
index e94b5da0e..a269daf53 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -25,6 +25,10 @@ parameters:
- phar://%currentWorkingDirectory%/test/unit/Fixture/autoload.phar/vendor/autoload.php
ignoreErrors:
+ -
+ identifier: class.notFound
+ message: '#(unknown class|invalid type) PropertyHookType#'
+
-
identifier: missingType.generics
-
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 5785c6199..7796eec1a 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -144,11 +144,15 @@
+
+
+
+
@@ -201,14 +205,19 @@
+
+
+
+
+
diff --git a/src/Reflection/Adapter/ReflectionClass.php b/src/Reflection/Adapter/ReflectionClass.php
index 64bbe2ed5..6778e8511 100644
--- a/src/Reflection/Adapter/ReflectionClass.php
+++ b/src/Reflection/Adapter/ReflectionClass.php
@@ -33,6 +33,12 @@
*/
final class ReflectionClass extends CoreReflectionClass
{
+ /** @internal */
+ public const SKIP_INITIALIZATION_ON_SERIALIZE_COMPATIBILITY = 8;
+
+ /** @internal */
+ public const SKIP_DESTRUCTOR_COMPATIBILITY = 16;
+
public function __construct(private BetterReflectionClass|BetterReflectionEnum $betterReflectionClass)
{
unset($this->name);
@@ -450,6 +456,50 @@ public function newInstanceArgs(array|null $args = null): object
throw new Exception\NotImplemented('Not implemented');
}
+ /** @param int-mask-of $options */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function initializeLazyObject(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
/** @psalm-mutation-free */
public function getParentClass(): ReflectionClass|false
{
diff --git a/src/Reflection/Adapter/ReflectionClassConstant.php b/src/Reflection/Adapter/ReflectionClassConstant.php
index 90432db64..3ddde192a 100644
--- a/src/Reflection/Adapter/ReflectionClassConstant.php
+++ b/src/Reflection/Adapter/ReflectionClassConstant.php
@@ -160,6 +160,11 @@ public function isEnumCase(): bool
return $this->betterClassConstantOrEnumCase instanceof BetterReflectionEnumCase;
}
+ public function isDeprecated(): bool
+ {
+ return $this->betterClassConstantOrEnumCase->isDeprecated();
+ }
+
public function __get(string $name): mixed
{
if ($name === 'name') {
diff --git a/src/Reflection/Adapter/ReflectionEnum.php b/src/Reflection/Adapter/ReflectionEnum.php
index 4b475ec79..58bfb4fd4 100644
--- a/src/Reflection/Adapter/ReflectionEnum.php
+++ b/src/Reflection/Adapter/ReflectionEnum.php
@@ -376,6 +376,50 @@ public function newInstanceArgs(array|null $args = null): object
throw new Exception\NotImplemented('Not implemented');
}
+ /** @param int-mask-of $options */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function initializeLazyObject(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
public function getParentClass(): ReflectionClass|false
{
return false;
diff --git a/src/Reflection/Adapter/ReflectionEnumBackedCase.php b/src/Reflection/Adapter/ReflectionEnumBackedCase.php
index 47e3aebcd..f4391baec 100644
--- a/src/Reflection/Adapter/ReflectionEnumBackedCase.php
+++ b/src/Reflection/Adapter/ReflectionEnumBackedCase.php
@@ -129,6 +129,11 @@ public function getBackingValue(): int|string
return $this->betterReflectionEnumCase->getValue();
}
+ public function isDeprecated(): bool
+ {
+ return $this->betterReflectionEnumCase->isDeprecated();
+ }
+
public function __get(string $name): mixed
{
if ($name === 'name') {
diff --git a/src/Reflection/Adapter/ReflectionEnumUnitCase.php b/src/Reflection/Adapter/ReflectionEnumUnitCase.php
index f905812fc..1521a6273 100644
--- a/src/Reflection/Adapter/ReflectionEnumUnitCase.php
+++ b/src/Reflection/Adapter/ReflectionEnumUnitCase.php
@@ -124,6 +124,11 @@ public function getEnum(): ReflectionEnum
return new ReflectionEnum($this->betterReflectionEnumCase->getDeclaringEnum());
}
+ public function isDeprecated(): bool
+ {
+ return $this->betterReflectionEnumCase->isDeprecated();
+ }
+
public function __get(string $name): mixed
{
if ($name === 'name') {
diff --git a/src/Reflection/Adapter/ReflectionNamedType.php b/src/Reflection/Adapter/ReflectionNamedType.php
index 80d97e3de..b83111f5e 100644
--- a/src/Reflection/Adapter/ReflectionNamedType.php
+++ b/src/Reflection/Adapter/ReflectionNamedType.php
@@ -12,29 +12,48 @@
/** @psalm-immutable */
final class ReflectionNamedType extends CoreReflectionNamedType
{
- public function __construct(private BetterReflectionNamedType $betterReflectionType, private bool $allowsNull)
+ /** @var non-empty-string */
+ private string $nameType;
+
+ private bool $isBuiltin;
+
+ /** @var non-empty-string */
+ private string $toString;
+
+ /** @param \Roave\BetterReflection\Reflection\ReflectionNamedType|non-empty-string $type */
+ public function __construct(BetterReflectionNamedType|string $type, private bool $allowsNull)
{
+ if ($type instanceof BetterReflectionNamedType) {
+ $nameType = $type->getName();
+ $this->nameType = $nameType;
+ $this->isBuiltin = $this->computeIsBuiltin($nameType, $type->isBuiltin());
+ $this->toString = $type->__toString();
+ } else {
+ $this->nameType = $type;
+ $this->isBuiltin = true;
+ $this->toString = $type;
+ }
}
public function getName(): string
{
- return $this->betterReflectionType->getName();
+ return $this->nameType;
}
/** @return non-empty-string */
public function __toString(): string
{
- $type = strtolower($this->betterReflectionType->getName());
+ $normalizedType = strtolower($this->nameType);
if (
! $this->allowsNull
- || $type === 'mixed'
- || $type === 'null'
+ || $normalizedType === 'mixed'
+ || $normalizedType === 'null'
) {
- return $this->betterReflectionType->__toString();
+ return $this->toString;
}
- return '?' . $this->betterReflectionType->__toString();
+ return '?' . $this->toString;
}
public function allowsNull(): bool
@@ -44,12 +63,17 @@ public function allowsNull(): bool
public function isBuiltin(): bool
{
- $type = strtolower($this->betterReflectionType->getName());
+ return $this->isBuiltin;
+ }
+
+ private function computeIsBuiltin(string $namedType, bool $isBuiltin): bool
+ {
+ $normalizedType = strtolower($namedType);
- if ($type === 'self' || $type === 'parent' || $type === 'static') {
+ if ($normalizedType === 'self' || $normalizedType === 'parent' || $normalizedType === 'static') {
return false;
}
- return $this->betterReflectionType->isBuiltin();
+ return $isBuiltin;
}
}
diff --git a/src/Reflection/Adapter/ReflectionObject.php b/src/Reflection/Adapter/ReflectionObject.php
index 6221d013c..88f0efca4 100644
--- a/src/Reflection/Adapter/ReflectionObject.php
+++ b/src/Reflection/Adapter/ReflectionObject.php
@@ -396,6 +396,50 @@ public function newInstanceArgs(array|null $args = null): ReflectionObject
throw new Exception\NotImplemented('Not implemented');
}
+ /** @param int-mask-of $options */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function initializeLazyObject(object $object): object
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
/** @psalm-mutation-free */
public function getParentClass(): ReflectionClass|false
{
diff --git a/src/Reflection/Adapter/ReflectionProperty.php b/src/Reflection/Adapter/ReflectionProperty.php
index 6f009056e..fe7112055 100644
--- a/src/Reflection/Adapter/ReflectionProperty.php
+++ b/src/Reflection/Adapter/ReflectionProperty.php
@@ -6,12 +6,16 @@
use ArgumentCountError;
use OutOfBoundsException;
+use PropertyHookType;
use ReflectionException as CoreReflectionException;
+use ReflectionMethod as CoreReflectionMethod;
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\ReflectionMethod as BetterReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
+use Roave\BetterReflection\Reflection\ReflectionPropertyHookType as BetterReflectionPropertyHookType;
use Throwable;
use TypeError;
use ValueError;
@@ -23,6 +27,21 @@
/** @psalm-suppress PropertyNotSetInConstructor */
final class ReflectionProperty extends CoreReflectionProperty
{
+ /** @internal */
+ public const IS_FINAL_COMPATIBILITY = 32;
+
+ /** @internal */
+ public const IS_ABSTRACT_COMPATIBILITY = 64;
+
+ /** @internal */
+ public const IS_VIRTUAL_COMPATIBILITY = 512;
+
+ /** @internal */
+ public const IS_PROTECTED_SET_COMPATIBILITY = 2048;
+
+ /** @internal */
+ public const IS_PRIVATE_SET_COMPATIBILITY = 4096;
+
public function __construct(private BetterReflectionProperty $betterReflectionProperty)
{
unset($this->name);
@@ -66,6 +85,21 @@ public function setValue(mixed $objectOrValue, mixed $value = null): void
}
}
+ public function setRawValueWithoutLazyInitialization(object $object, mixed $value): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function isLazy(object $object): bool
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function skipLazyInitialization(object $object): void
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
/** @psalm-mutation-free */
public function hasType(): bool
{
@@ -90,24 +124,54 @@ public function isPrivate(): bool
return $this->betterReflectionProperty->isPrivate();
}
+ /** @psalm-mutation-free */
+ public function isPrivateSet(): bool
+ {
+ return $this->betterReflectionProperty->isPrivateSet();
+ }
+
/** @psalm-mutation-free */
public function isProtected(): bool
{
return $this->betterReflectionProperty->isProtected();
}
+ /** @psalm-mutation-free */
+ public function isProtectedSet(): bool
+ {
+ return $this->betterReflectionProperty->isProtectedSet();
+ }
+
/** @psalm-mutation-free */
public function isStatic(): bool
{
return $this->betterReflectionProperty->isStatic();
}
+ /** @psalm-mutation-free */
+ public function isFinal(): bool
+ {
+ return $this->betterReflectionProperty->isFinal();
+ }
+
+ /** @psalm-mutation-free */
+ public function isAbstract(): bool
+ {
+ return $this->betterReflectionProperty->isAbstract();
+ }
+
/** @psalm-mutation-free */
public function isDefault(): bool
{
return $this->betterReflectionProperty->isDefault();
}
+ /** @psalm-mutation-free */
+ public function isDynamic(): bool
+ {
+ return $this->betterReflectionProperty->isDynamic();
+ }
+
/** @psalm-mutation-free */
public function getModifiers(): int
{
@@ -190,6 +254,71 @@ public function isReadOnly(): bool
return $this->betterReflectionProperty->isReadOnly();
}
+ /** @psalm-mutation-free */
+ public function isVirtual(): bool
+ {
+ return $this->betterReflectionProperty->isVirtual();
+ }
+
+ public function hasHooks(): bool
+ {
+ return $this->betterReflectionProperty->hasHooks();
+ }
+
+ /** @psalm-suppress UndefinedClass */
+ public function hasHook(PropertyHookType $hookType): bool
+ {
+ return $this->betterReflectionProperty->hasHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
+ }
+
+ /** @psalm-suppress UndefinedClass */
+ public function getHook(PropertyHookType $hookType): ReflectionMethod|null
+ {
+ $hook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
+ if ($hook === null) {
+ return null;
+ }
+
+ return new ReflectionMethod($hook);
+ }
+
+ /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ public function getHooks(): array
+ {
+ return array_map(
+ static fn (BetterReflectionMethod $betterReflectionMethod): CoreReflectionMethod => new ReflectionMethod($betterReflectionMethod),
+ $this->betterReflectionProperty->getHooks(),
+ );
+ }
+
+ public function getSettableType(): ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
+ {
+ $setHook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::Set);
+ if ($setHook !== null) {
+ return ReflectionType::fromTypeOrNull($setHook->getParameters()[0]->getType());
+ }
+
+ if ($this->isVirtual()) {
+ return new ReflectionNamedType('never', false);
+ }
+
+ return $this->getType();
+ }
+
+ public function getRawValue(object $object): mixed
+ {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ public function setRawValue(object $object, mixed $value): void
+ {
+ if ($this->hasHooks()) {
+ throw new Exception\NotImplemented('Not implemented');
+ }
+
+ $this->setValue($object, $value);
+ }
+
public function __get(string $name): mixed
{
if ($name === 'name') {
diff --git a/src/Reflection/Deprecated/DeprecatedHelper.php b/src/Reflection/Deprecated/DeprecatedHelper.php
new file mode 100644
index 000000000..85e57f022
--- /dev/null
+++ b/src/Reflection/Deprecated/DeprecatedHelper.php
@@ -0,0 +1,29 @@
+getAttributes(), 'Deprecated') !== []) {
+ return true;
+ }
+
+ return AnnotationHelper::isDeprecated($reflection->getDocComment());
+ }
+}
diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php
index 111a26b51..10939789a 100644
--- a/src/Reflection/ReflectionClass.php
+++ b/src/Reflection/ReflectionClass.php
@@ -21,8 +21,8 @@
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionMethod as ReflectionMethodAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter;
-use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper;
use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
+use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
use Roave\BetterReflection\Reflection\Exception\CircularReference;
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
@@ -571,6 +571,7 @@ private function createImmediateMethods(ClassNode|InterfaceNode|TraitNode|EnumNo
$reflector,
$methodNode,
$this->locatedSource,
+ $methodNode->name->name,
$this->getNamespaceName(),
$this,
$this,
@@ -596,6 +597,8 @@ private function addEnumMethods(EnumNode $node, array $methods): array
{
$internalLocatedSource = new InternalLocatedSource('', $this->getName(), 'Core');
$createMethod = function (string $name, array $params, Node\Identifier|Node\NullableType $returnType) use ($internalLocatedSource): ReflectionMethod {
+ assert($name !== '');
+
/** @var array{flags: int, params: Node\Param[], returnType: Node\Identifier|Node\NullableType} $classMethodSubnodes */
$classMethodSubnodes = [
'flags' => Modifiers::PUBLIC | Modifiers::STATIC,
@@ -610,6 +613,7 @@ private function addEnumMethods(EnumNode $node, array $methods): array
$classMethodSubnodes,
),
$internalLocatedSource,
+ $name,
$this->getNamespaceName(),
$this,
$this,
@@ -1191,7 +1195,7 @@ public function isUserDefined(): bool
public function isDeprecated(): bool
{
- return AnnotationHelper::isDeprecated($this->docComment);
+ return DeprecatedHelper::isDeprecated($this);
}
/**
diff --git a/src/Reflection/ReflectionClassConstant.php b/src/Reflection/ReflectionClassConstant.php
index 33b09acaf..5c1223f08 100644
--- a/src/Reflection/ReflectionClassConstant.php
+++ b/src/Reflection/ReflectionClassConstant.php
@@ -11,8 +11,8 @@
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
-use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper;
use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
+use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
use Roave\BetterReflection\Reflection\StringCast\ReflectionClassConstantStringCast;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\Util\CalculateReflectionColumn;
@@ -265,7 +265,7 @@ public function getDocComment(): string|null
public function isDeprecated(): bool
{
- return AnnotationHelper::isDeprecated($this->getDocComment());
+ return DeprecatedHelper::isDeprecated($this);
}
/** @return non-empty-string */
diff --git a/src/Reflection/ReflectionEnumCase.php b/src/Reflection/ReflectionEnumCase.php
index 1b45d139a..9bd1ac361 100644
--- a/src/Reflection/ReflectionEnumCase.php
+++ b/src/Reflection/ReflectionEnumCase.php
@@ -10,8 +10,8 @@
use Roave\BetterReflection\NodeCompiler\CompiledValue;
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\Deprecated\DeprecatedHelper;
use Roave\BetterReflection\Reflection\StringCast\ReflectionEnumCaseStringCast;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\Util\CalculateReflectionColumn;
@@ -172,7 +172,7 @@ public function getDocComment(): string|null
public function isDeprecated(): bool
{
- return AnnotationHelper::isDeprecated($this->docComment);
+ return DeprecatedHelper::isDeprecated($this);
}
/** @return list */
diff --git a/src/Reflection/ReflectionFunctionAbstract.php b/src/Reflection/ReflectionFunctionAbstract.php
index ddfe5e68c..bb44a14e0 100644
--- a/src/Reflection/ReflectionFunctionAbstract.php
+++ b/src/Reflection/ReflectionFunctionAbstract.php
@@ -13,6 +13,7 @@
use PhpParser\NodeVisitor\FindingVisitor;
use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper;
use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
+use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
use Roave\BetterReflection\Reflection\Exception\CodeLocationMissing;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use Roave\BetterReflection\Util\CalculateReflectionColumn;
@@ -97,7 +98,7 @@ abstract public function __toString(): string;
abstract public function getShortName(): string;
/** @psalm-external-mutation-free */
- private function fillFromNode(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): void
+ private function fillFromNode(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): void
{
$this->parameters = $this->createParameters($node);
$this->returnsReference = $node->returnsByRef();
@@ -135,7 +136,7 @@ private function fillFromNode(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|N
}
/** @return array */
- private function createParameters(Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): array
+ private function createParameters(Node\Stmt\ClassMethod|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): array
{
$parameters = [];
@@ -283,7 +284,7 @@ public function isClosure(): bool
public function isDeprecated(): bool
{
- return AnnotationHelper::isDeprecated($this->docComment);
+ return DeprecatedHelper::isDeprecated($this);
}
public function isInternal(): bool
@@ -326,7 +327,7 @@ public function couldThrow(): bool
return $this->couldThrow;
}
- private function computeCouldThrow(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): bool
+ private function computeCouldThrow(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): bool
{
$statements = $node->getStmts();
@@ -497,7 +498,7 @@ public function getTentativeReturnType(): ReflectionNamedType|ReflectionUnionTyp
return $this->returnType;
}
- private function createReturnType(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null
+ private function createReturnType(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null
{
$returnType = $node->getReturnType();
diff --git a/src/Reflection/ReflectionMethod.php b/src/Reflection/ReflectionMethod.php
index 19ac618ed..dd2259cdb 100644
--- a/src/Reflection/ReflectionMethod.php
+++ b/src/Reflection/ReflectionMethod.php
@@ -34,23 +34,25 @@ class ReflectionMethod
private int $modifiers;
/**
+ * @param non-empty-string $name
* @param non-empty-string|null $aliasName
* @param non-empty-string|null $namespace
*/
private function __construct(
private Reflector $reflector,
- MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node,
+ MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node,
private LocatedSource $locatedSource,
+ string $name,
private string|null $namespace,
private ReflectionClass $declaringClass,
private ReflectionClass $implementingClass,
private ReflectionClass $currentClass,
private string|null $aliasName,
) {
- assert($node instanceof MethodNode);
+ assert($node instanceof MethodNode || $node instanceof Node\PropertyHook);
- $this->name = $node->name->name;
- $this->modifiers = $this->computeModifiers($node);
+ $this->name = $name;
+ $this->modifiers = $node instanceof MethodNode ? $this->computeModifiers($node) : 0;
$this->fillFromNode($node);
}
@@ -58,13 +60,15 @@ private function __construct(
/**
* @internal
*
+ * @param non-empty-string $name
* @param non-empty-string|null $aliasName
* @param non-empty-string|null $namespace
*/
public static function createFromNode(
Reflector $reflector,
- MethodNode $node,
+ MethodNode|Node\PropertyHook $node,
LocatedSource $locatedSource,
+ string $name,
string|null $namespace,
ReflectionClass $declaringClass,
ReflectionClass $implementingClass,
@@ -75,6 +79,7 @@ public static function createFromNode(
$reflector,
$node,
$locatedSource,
+ $name,
$namespace,
$declaringClass,
$implementingClass,
diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php
index dd56af161..28e9ff110 100644
--- a/src/Reflection/ReflectionProperty.php
+++ b/src/Reflection/ReflectionProperty.php
@@ -7,16 +7,19 @@
use Closure;
use Error;
use OutOfBoundsException;
+use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property as PropertyNode;
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\FindingVisitor;
use ReflectionException;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\NodeCompiler\CompiledValue;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter;
-use Roave\BetterReflection\Reflection\Annotation\AnnotationHelper;
use Roave\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
+use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
use Roave\BetterReflection\Reflection\Exception\CodeLocationMissing;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
@@ -68,6 +71,20 @@ class ReflectionProperty
/** @var positive-int|null */
private int|null $endColumn;
+ private bool $immediateVirtual;
+
+ /** @var array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ private array $immediateHooks;
+
+ /**
+ * @var array{get?: ReflectionMethod, set?: ReflectionMethod}|null
+ * @psalm-allow-private-mutation
+ */
+ private array|null $cachedHooks = null;
+
+ /** @psalm-allow-private-mutation */
+ private bool|null $cachedVirtual = null;
+
/** @psalm-allow-private-mutation */
private CompiledValue|null $compiledDefaultValue = null;
@@ -80,12 +97,14 @@ private function __construct(
private bool $isPromoted,
private bool $declaredAtCompileTime,
) {
- $this->name = $propertyNode->name->name;
- $this->modifiers = $this->computeModifiers($node);
- $this->type = $this->createType($node);
- $this->default = $propertyNode->default;
- $this->docComment = GetLastDocComment::forNode($node);
- $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups);
+ $this->name = $propertyNode->name->name;
+ $this->modifiers = $this->computeModifiers($node);
+ $this->type = $this->createType($node);
+ $this->default = $propertyNode->default;
+ $this->docComment = GetLastDocComment::forNode($node);
+ $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups);
+ $this->immediateVirtual = $this->computeImmediateVirtual($node);
+ $this->immediateHooks = $this->createImmediateHooks($node);
$startLine = $node->getStartLine();
if ($startLine === -1) {
@@ -213,6 +232,11 @@ public function isDefault(): bool
return $this->declaredAtCompileTime;
}
+ public function isDynamic(): bool
+ {
+ return ! $this->isDefault();
+ }
+
/**
* Get the core-reflection-compatible modifier values.
*
@@ -220,7 +244,11 @@ public function isDefault(): bool
*/
public function getModifiers(): int
{
- return $this->modifiers;
+ /** @var int-mask-of $modifiers */
+ $modifiers = $this->modifiers
+ + ($this->isVirtual() ? ReflectionPropertyAdapter::IS_VIRTUAL_COMPATIBILITY : 0);
+
+ return $modifiers;
}
/**
@@ -241,6 +269,11 @@ public function isPrivate(): bool
return ($this->modifiers & CoreReflectionProperty::IS_PRIVATE) === CoreReflectionProperty::IS_PRIVATE;
}
+ public function isPrivateSet(): bool
+ {
+ return ($this->modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY) === ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY;
+ }
+
/**
* Is the property protected?
*/
@@ -249,6 +282,11 @@ public function isProtected(): bool
return ($this->modifiers & CoreReflectionProperty::IS_PROTECTED) === CoreReflectionProperty::IS_PROTECTED;
}
+ public function isProtectedSet(): bool
+ {
+ return ($this->modifiers & ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY) === ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY;
+ }
+
/**
* Is the property public?
*/
@@ -265,6 +303,16 @@ public function isStatic(): bool
return ($this->modifiers & CoreReflectionProperty::IS_STATIC) === CoreReflectionProperty::IS_STATIC;
}
+ public function isFinal(): bool
+ {
+ return ($this->modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY) === ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY;
+ }
+
+ public function isAbstract(): bool
+ {
+ return ($this->modifiers & ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY) === ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY;
+ }
+
public function isPromoted(): bool
{
return $this->isPromoted;
@@ -353,7 +401,7 @@ public function getDefaultValue(): string|int|float|bool|array|null
public function isDeprecated(): bool
{
- return AnnotationHelper::isDeprecated($this->getDocComment());
+ return DeprecatedHelper::isDeprecated($this);
}
/**
@@ -548,6 +596,36 @@ public function hasType(): bool
return $this->type !== null;
}
+ public function isVirtual(): bool
+ {
+ $this->cachedVirtual ??= $this->createCachedVirtual();
+
+ return $this->cachedVirtual;
+ }
+
+ public function hasHooks(): bool
+ {
+ return $this->getHooks() !== [];
+ }
+
+ public function hasHook(ReflectionPropertyHookType $hookType): bool
+ {
+ return isset($this->getHooks()[$hookType->value]);
+ }
+
+ public function getHook(ReflectionPropertyHookType $hookType): ReflectionMethod|null
+ {
+ return $this->getHooks()[$hookType->value] ?? null;
+ }
+
+ /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ public function getHooks(): array
+ {
+ $this->cachedHooks ??= $this->createCachedHooks();
+
+ return $this->cachedHooks;
+ }
+
/**
* @param class-string $className
*
@@ -592,9 +670,118 @@ private function computeModifiers(PropertyNode $node): int
$modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY : 0;
$modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
$modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;
+ $modifiers += $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0;
$modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
+ $modifiers += $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
$modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
+ $modifiers += ($node->flags & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY) === ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
+ $modifiers += ($node->flags & Modifiers::ABSTRACT) === Modifiers::ABSTRACT ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
+ /** @phpstan-ignore return.type */
return $modifiers;
}
+
+ private function computeImmediateVirtual(PropertyNode $node): bool
+ {
+ if ($node->hooks === []) {
+ return false;
+ }
+
+ $setHook = null;
+
+ foreach ($node->hooks as $hook) {
+ if ($hook->name->name === 'set') {
+ $setHook = $hook;
+ break;
+ }
+ }
+
+ if ($setHook === null) {
+ return true;
+ }
+
+ $setHookBody = $setHook->getStmts();
+ assert($setHookBody !== null);
+
+ $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\Assign);
+ $traverser = new NodeTraverser($visitor);
+ $traverser->traverse($setHookBody);
+
+ foreach ($visitor->getFoundNodes() as $assigNode) {
+ assert($assigNode instanceof Node\Expr\Assign);
+ $variableToAssign = $assigNode->var;
+
+ if (
+ $variableToAssign instanceof Node\Expr\PropertyFetch
+ && $variableToAssign->var instanceof Node\Expr\Variable
+ && $variableToAssign->var->name === 'this'
+ && $variableToAssign->name instanceof Node\Identifier
+ && $variableToAssign->name->name === $this->name
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ private function createImmediateHooks(PropertyNode $node): array
+ {
+ $hooks = [];
+
+ foreach ($node->hooks as $hook) {
+ $hookName = $hook->name->name;
+ assert($hookName === 'get' || $hookName === 'set');
+
+ $hooks[$hookName] = ReflectionMethod::createFromNode(
+ $this->reflector,
+ $hook,
+ $this->getDeclaringClass()->getLocatedSource(),
+ sprintf('$%s::%s', $this->name, $hookName),
+ null,
+ $this->getDeclaringClass(),
+ $this->getImplementingClass(),
+ $this->getDeclaringClass(),
+ );
+ }
+
+ return $hooks;
+ }
+
+ private function createCachedVirtual(): bool
+ {
+ if (! $this->immediateVirtual) {
+ return false;
+ }
+
+ return $this->getParentProperty()?->isVirtual() ?? $this->immediateVirtual;
+ }
+
+ /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ private function createCachedHooks(): array
+ {
+ $hooks = $this->immediateHooks;
+
+ if (isset($hooks['get'], $hooks['set'])) {
+ return $hooks;
+ }
+
+ $parentHooks = $this->getParentProperty()?->getHooks() ?? [];
+
+ foreach ($parentHooks as $hookName => $parentHook) {
+ if (isset($hooks[$hookName])) {
+ continue;
+ }
+
+ $hooks[$hookName] = $parentHook;
+ }
+
+ return $hooks;
+ }
+
+ private function getParentProperty(): ReflectionProperty|null
+ {
+ return $this->getDeclaringClass()->getParentClass()?->getProperty($this->name);
+ }
}
diff --git a/src/Reflection/ReflectionPropertyHookType.php b/src/Reflection/ReflectionPropertyHookType.php
new file mode 100644
index 000000000..856ec9de2
--- /dev/null
+++ b/src/Reflection/ReflectionPropertyHookType.php
@@ -0,0 +1,23 @@
+ self::Get,
+ CoreReflectionPropertyHookType::Set => self::Set,
+ };
+ }
+}
diff --git a/src/SourceLocator/Located/InternalLocatedSource.php b/src/SourceLocator/Located/InternalLocatedSource.php
index 2018697ca..5ad730585 100644
--- a/src/SourceLocator/Located/InternalLocatedSource.php
+++ b/src/SourceLocator/Located/InternalLocatedSource.php
@@ -12,7 +12,7 @@
class InternalLocatedSource extends LocatedSource
{
/** @param non-empty-string $extensionName */
- public function __construct(string $source, string $name, private string $extensionName)
+ public function __construct(string $source, string $name, private string $extensionName, private string|null $aliasName = null)
{
parent::__construct($source, $name);
}
@@ -27,4 +27,9 @@ public function getExtensionName(): string|null
{
return $this->extensionName;
}
+
+ public function getAliasName(): string|null
+ {
+ return $this->aliasName;
+ }
}
diff --git a/src/SourceLocator/Type/PhpInternalSourceLocator.php b/src/SourceLocator/Type/PhpInternalSourceLocator.php
index c789685ac..0fc6a41f5 100644
--- a/src/SourceLocator/Type/PhpInternalSourceLocator.php
+++ b/src/SourceLocator/Type/PhpInternalSourceLocator.php
@@ -5,6 +5,7 @@
namespace Roave\BetterReflection\SourceLocator\Type;
use InvalidArgumentException;
+use ReflectionClass as CoreReflectionClass;
use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\SourceLocator\Ast\Locator;
use Roave\BetterReflection\SourceLocator\Exception\InvalidFileLocation;
@@ -13,6 +14,9 @@
use Roave\BetterReflection\SourceLocator\SourceStubber\SourceStubber;
use Roave\BetterReflection\SourceLocator\SourceStubber\StubData;
+use function class_exists;
+use function strtolower;
+
final class PhpInternalSourceLocator extends AbstractSourceLocator
{
public function __construct(Locator $astLocator, private SourceStubber $stubber)
@@ -41,8 +45,19 @@ private function getClassSource(Identifier $identifier): InternalLocatedSource|n
/** @psalm-var class-string|trait-string $className */
$className = $identifier->getName();
+ $aliasName = null;
+
+ if (class_exists($className, false)) {
+ $reflectionClass = new CoreReflectionClass($className);
+
+ if (strtolower($reflectionClass->getName()) !== strtolower($className)) {
+ $aliasName = $className;
+ $className = $reflectionClass->getName();
+ $identifier = new Identifier($className, $identifier->getType());
+ }
+ }
- return $this->createLocatedSourceFromStubData($identifier, $this->stubber->generateClassStub($className));
+ return $this->createLocatedSourceFromStubData($identifier, $this->stubber->generateClassStub($className), $aliasName);
}
private function getFunctionSource(Identifier $identifier): InternalLocatedSource|null
@@ -63,7 +78,7 @@ private function getConstantSource(Identifier $identifier): InternalLocatedSourc
return $this->createLocatedSourceFromStubData($identifier, $this->stubber->generateConstantStub($identifier->getName()));
}
- private function createLocatedSourceFromStubData(Identifier $identifier, StubData|null $stubData): InternalLocatedSource|null
+ private function createLocatedSourceFromStubData(Identifier $identifier, StubData|null $stubData, string|null $aliasName = null): InternalLocatedSource|null
{
if ($stubData === null) {
return null;
@@ -80,6 +95,7 @@ private function createLocatedSourceFromStubData(Identifier $identifier, StubDat
$stubData->getStub(),
$identifier->getName(),
$extensionName,
+ $aliasName,
);
}
}
diff --git a/test/unit/Fixture/AsymetricVisibilityClass.php b/test/unit/Fixture/AsymetricVisibilityClass.php
new file mode 100644
index 000000000..4e7bb1a0b
--- /dev/null
+++ b/test/unit/Fixture/AsymetricVisibilityClass.php
@@ -0,0 +1,25 @@
+ class Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass ] {
+ @@ %s/Fixture/AsymetricVisibilityClass.php 5-24
+
+ - Constants [0] {
+ }
+
+ - Static properties [0] {
+ }
+
+ - Static methods [0] {
+ }
+
+ - Properties [12] {
+ Property [ public string $publicPublicSet ]
+ Property [ public string $publicProtectedSet ]
+ Property [ public string $publicPrivateSet ]
+ Property [ protected int $protectedProtectedSet ]
+ Property [ protected int $protectedPrivateSet ]
+ Property [ private bool $privatePrivateSet ]
+ Property [ public string $promotedPublicPublicSet ]
+ Property [ public string $promotedPublicProtectedSet ]
+ Property [ public string $promotedPublicPrivateSet ]
+ Property [ protected int $promotedProtectedProtectedSet ]
+ Property [ protected int $promotedProtectedPrivateSet ]
+ Property [ private bool $promotedPrivatePrivateSet ]
+ }
+
+ - Methods [1] {
+ Method [ public method __construct ] {
+ @@ %s/Fixture/AsymetricVisibilityClass.php 14 - 23
+
+ - Parameters [6] {
+ Parameter #0 [ string $promotedPublicPublicSet ]
+ Parameter #1 [ string $promotedPublicProtectedSet ]
+ Parameter #2 [ string $promotedPublicPrivateSet ]
+ Parameter #3 [ int $promotedProtectedProtectedSet ]
+ Parameter #4 [ int $promotedProtectedPrivateSet ]
+ Parameter #5 [ bool $promotedPrivatePrivateSet ]
+ }
+ }
+ }
+}
diff --git a/test/unit/Fixture/ExampleClass.php b/test/unit/Fixture/ExampleClass.php
index 3178c6653..5db6c8717 100644
--- a/test/unit/Fixture/ExampleClass.php
+++ b/test/unit/Fixture/ExampleClass.php
@@ -43,6 +43,8 @@ class ExampleClass
public readonly int $readOnlyProperty;
+ final public int $finalPublicProperty = 123;
+
public static $publicStaticProperty;
protected static $protectedStaticProperty;
diff --git a/test/unit/Fixture/ExampleClassExport.txt b/test/unit/Fixture/ExampleClassExport.txt
index c5bd4a3e4..236a1f251 100644
--- a/test/unit/Fixture/ExampleClassExport.txt
+++ b/test/unit/Fixture/ExampleClassExport.txt
@@ -1,5 +1,5 @@
Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] {
- @@ %s/test/unit/Fixture/ExampleClass.php 10-59
+ @@ %s/test/unit/Fixture/ExampleClass.php 10-61
- Constants [8] {
Constant [ public integer MY_CONST_1 ] { 123 }
@@ -21,18 +21,19 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] {
- Static methods [0] {
}
- - Properties [6] {
+ - Properties [7] {
Property [ private $privateProperty ]
Property [ protected $protectedProperty ]
Property [ public $publicProperty ]
Property [ public readonly int $readOnlyProperty ]
+ Property [ public int $finalPublicProperty ]
Property [ private ?int $promotedProperty ]
Property [ private $promotedProperty2 ]
}
- Methods [2] {
Method [ public method __construct ] {
- @@ %s/test/unit/Fixture/ExampleClass.php 52 - 54
+ @@ %s/test/unit/Fixture/ExampleClass.php 54 - 56
- Parameters [3] {
Parameter #0 [ ?int $promotedProperty = 123 ]
@@ -42,7 +43,7 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] {
}
Method [ public method someMethod ] {
- @@ %s/test/unit/Fixture/ExampleClass.php 56 - 58
+ @@ %s/test/unit/Fixture/ExampleClass.php 58 - 60
}
}
}
diff --git a/test/unit/Fixture/PropertyHooks.php b/test/unit/Fixture/PropertyHooks.php
new file mode 100644
index 000000000..0729b3e4e
--- /dev/null
+++ b/test/unit/Fixture/PropertyHooks.php
@@ -0,0 +1,72 @@
+writeOnlyHook = $value;
+ }
+ }
+
+ public string $readAndWriteHook {
+ get {
+ $this->readAndWriteHook;
+ }
+ set (string $value) {
+ $this->readAndWriteHook = $value;
+ }
+ }
+
+ public string $virtualBecauseOfStrangeSetHook {
+ set (string $value) {
+ $this->differentProperty = $value;
+ }
+ }
+}
+
+abstract class AbstractPropertyHooks
+{
+ abstract public string $hook { get; }
+}
+
+class GetPropertyHook extends AbstractPropertyHooks
+{
+ public string $hook {
+ get {
+ return 'hook';
+ }
+ }
+}
+
+class GetAndSetPropertyHook extends GetPropertyHook
+{
+ public string $hook {
+ set (string $value) {
+ $this->hook = $value;
+ }
+ }
+}
+
+trait PropertyHookTrait
+{
+ public string $hook {
+ get {
+ return 'hook';
+ }
+ }
+}
+
+class UsePropertyHookFromTrait
+{
+ use PropertyHookTrait;
+}
diff --git a/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php b/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php
index 5a09ec314..e83487f6a 100644
--- a/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionClassConstantTest.php
@@ -61,6 +61,7 @@ public static function methodExpectationProvider(): array
['getDocComment', null, null, []],
['getAttributes', null, [], []],
['isFinal', null, true, []],
+ ['isDeprecated', null, true, []],
];
}
diff --git a/test/unit/Reflection/Adapter/ReflectionClassTest.php b/test/unit/Reflection/Adapter/ReflectionClassTest.php
index 75e0f2db9..08abadc02 100644
--- a/test/unit/Reflection/Adapter/ReflectionClassTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionClassTest.php
@@ -1286,4 +1286,92 @@ public function testGetTraits(): void
self::assertArrayHasKey($traitOneClassName, $traits);
self::assertArrayHasKey($traitTwoClassName, $traits);
}
+
+ public function testNewLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->resetAsLazyProxy(new stdClass(), static fn () => null);
+ }
}
diff --git a/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php b/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php
index deaab9481..620342a81 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php
@@ -54,6 +54,7 @@ public static function methodExpectationProvider(): array
['getValue', NotImplemented::class, null, []],
['getDocComment', null, null, []],
['getAttributes', null, [], []],
+ ['isDeprecated', null, true, []],
];
}
diff --git a/test/unit/Reflection/Adapter/ReflectionEnumTest.php b/test/unit/Reflection/Adapter/ReflectionEnumTest.php
index 4f0f2f3a1..821c61cd8 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumTest.php
@@ -1140,4 +1140,92 @@ public function testGetPropertiesWithFilter(): void
self::assertCount(1, $reflectionEnumAdapter->getProperties(CoreReflectionProperty::IS_PRIVATE));
self::assertCount(1, $reflectionEnumAdapter->getProperties(CoreReflectionProperty::IS_PROTECTED));
}
+
+ public function testNewLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->resetAsLazyProxy(new stdClass(), static fn () => null);
+ }
}
diff --git a/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php b/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php
index 103b5a344..b07e1d964 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php
@@ -54,6 +54,7 @@ public static function methodExpectationProvider(): array
['getValue', NotImplemented::class, null, []],
['getDocComment', null, null, []],
['getAttributes', null, [], []],
+ ['isDeprecated', null, true, []],
];
}
diff --git a/test/unit/Reflection/Adapter/ReflectionObjectTest.php b/test/unit/Reflection/Adapter/ReflectionObjectTest.php
index aa130483c..8cfa8a622 100644
--- a/test/unit/Reflection/Adapter/ReflectionObjectTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionObjectTest.php
@@ -1239,4 +1239,92 @@ public function testGetPropertiesWithFilter(): void
self::assertCount(1, $reflectionObjectAdapter->getProperties(CoreReflectionProperty::IS_PRIVATE));
self::assertCount(1, $reflectionObjectAdapter->getProperties(CoreReflectionProperty::IS_PROTECTED));
}
+
+ public function testNewLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->resetAsLazyProxy(new stdClass(), static fn () => null);
+ }
}
diff --git a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php
index f35f4a0f0..217a2ef1b 100644
--- a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php
@@ -8,10 +8,13 @@
use OutOfBoundsException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\TestCase;
+use PropertyHookType;
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\ReflectionNamedType as ReflectionNamedTypeAdapter;
@@ -21,7 +24,9 @@
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;
+use Roave\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
use Roave\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
use stdClass;
use TypeError;
@@ -60,9 +65,14 @@ public static function methodExpectationProvider(): array
['getName', [], 'name', null, 'name', null],
['isPublic', [], true, null, true, null],
['isPrivate', [], true, null, true, null],
+ ['isPrivateSet', [], true, null, true, null],
['isProtected', [], true, null, true, null],
+ ['isProtectedSet', [], true, null, true, null],
['isStatic', [], true, null, true, null],
+ ['isFinal', [], true, null, true, null],
+ ['isAbstract', [], true, null, true, null],
['isDefault', [], true, null, true, null],
+ ['isDynamic', [], false, null, false, null],
['getModifiers', [], 123, null, 123, null],
['getDocComment', [], null, null, false, null],
['hasType', [], true, null, true, null],
@@ -70,6 +80,9 @@ public static function methodExpectationProvider(): array
['getDefaultValue', [], null, null, null, null],
['isPromoted', [], true, null, true, null],
['isReadOnly', [], true, null, true, null],
+ ['isVirtual', [], true, null, true, null],
+ ['hasHooks', [], false, null, false, null],
+ ['getHooks', [], [], null, [], null],
];
}
@@ -460,4 +473,166 @@ public function testUnknownProperty(): void
/** @phpstan-ignore property.notFound, expr.resultUnused */
$reflectionPropertyAdapter->foo;
}
+
+ public function testSetRawValueWithoutLazyInitialization(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->setRawValueWithoutLazyInitialization(new stdClass(), null);
+ }
+
+ public function testIsLazy(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->isLazy(new stdClass());
+ }
+
+ public function testSkipLazyInitialization(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->skipLazyInitialization(new stdClass());
+ }
+
+ #[RequiresPhp('8.4')]
+ public function testHasAndGetHookWhenNoHooks(): void
+ {
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('hasHook')
+ ->willReturn(false);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn(null);
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertFalse($reflectionPropertyAdapter->hasHook(PropertyHookType::Get));
+ self::assertNull($reflectionPropertyAdapter->getHook(PropertyHookType::Get));
+ }
+
+ #[RequiresPhp('8.4')]
+ public function testHasAndGetHook(): void
+ {
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('hasHook')
+ ->willReturn(true);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn($this->createMock(BetterReflectionMethod::class));
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertTrue($reflectionPropertyAdapter->hasHook(PropertyHookType::Get));
+ self::assertNotNull($reflectionPropertyAdapter->getHook(PropertyHookType::Get));
+ }
+
+ public function testGetSettableType(): void
+ {
+ $setHookParameterType = $this->createMock(BetterReflectionNamedType::class);
+ $setHookParameterType
+ ->method('getName')
+ ->willReturn('int');
+
+ $setHookParameter = $this->createMock(BetterReflectionParameter::class);
+ $setHookParameter
+ ->method('getType')
+ ->willReturn($setHookParameterType);
+
+ $setHook = $this->createMock(BetterReflectionMethod::class);
+ $setHook
+ ->method('getParameters')
+ ->willReturn([$setHookParameter]);
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn($setHook);
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertInstanceOf(ReflectionNamedTypeAdapter::class, $reflectionPropertyAdapter->getSettableType());
+ self::assertSame('int', $reflectionPropertyAdapter->getSettableType()->getName());
+ }
+
+ public function testGetSettableTypeWhenVirtual(): void
+ {
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn(null);
+ $betterReflectionProperty
+ ->method('isVirtual')
+ ->willReturn(true);
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertInstanceOf(ReflectionNamedTypeAdapter::class, $reflectionPropertyAdapter->getSettableType());
+ self::assertSame('never', $reflectionPropertyAdapter->getSettableType()->getName());
+ }
+
+ public function testGetSettableTypeWhenNoHooks(): void
+ {
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn(null);
+ $betterReflectionProperty
+ ->method('isVirtual')
+ ->willReturn(false);
+ $betterReflectionProperty
+ ->method('getType')
+ ->willReturn($this->createMock(BetterReflectionNamedType::class));
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertInstanceOf(ReflectionNamedTypeAdapter::class, $reflectionPropertyAdapter->getSettableType());
+ }
+
+ public function testGetSettableTypeWhenNoHooksAndNoType(): void
+ {
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('getHook')
+ ->willReturn(null);
+ $betterReflectionProperty
+ ->method('isVirtual')
+ ->willReturn(false);
+ $betterReflectionProperty
+ ->method('getType')
+ ->willReturn(null);
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ self::assertNull($reflectionPropertyAdapter->getSettableType());
+ }
+
+ public function testSetRawValueWhenNoHooks(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $betterReflectionProperty
+ ->method('hasHooks')
+ ->willReturn(true);
+
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->setRawValue(new stdClass(), null);
+ }
+
+ public function testGetRawValue(): void
+ {
+ self::expectException(NotImplemented::class);
+ self::expectExceptionMessage('Not implemented');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->getRawValue(new stdClass());
+ }
}
diff --git a/test/unit/Reflection/Deprecated/DeprecatedHelperTest.php b/test/unit/Reflection/Deprecated/DeprecatedHelperTest.php
new file mode 100644
index 000000000..86f58e0fb
--- /dev/null
+++ b/test/unit/Reflection/Deprecated/DeprecatedHelperTest.php
@@ -0,0 +1,100 @@
+ */
+ public static function deprecatedAttributeProvider(): array
+ {
+ return [
+ [
+ '',
+ false,
+ ],
+ [
+ '#[SomeAttribute]',
+ false,
+ ],
+ [
+ '#[Deprecated]',
+ true,
+ ],
+ [
+ '#[Deprecated(since: "8.0.0")]',
+ true,
+ ],
+ [
+ '#[SomeAttribute] #[Deprecated]',
+ true,
+ ],
+ ];
+ }
+
+ #[DataProvider('deprecatedAttributeProvider')]
+ public function testIsDeprecatedByAttribute(string $deprecatedCode, bool $isDeprecated): void
+ {
+ $php = sprintf('astLocator()));
+ $reflection = $reflector->reflectClass('Foo');
+
+ self::assertSame($isDeprecated, DeprecatedHelper::isDeprecated($reflection));
+ }
+
+ /** @return list */
+ public static function deprecatedDocCommentProvider(): array
+ {
+ return [
+ [null, false],
+ ['', false],
+ [
+ '/**
+ * @return string
+ */',
+ false,
+ ],
+ [
+ '/**
+ * @deprecatedPolicy
+ */',
+ false,
+ ],
+ ['/** @deprecated */', true],
+ ['/**@deprecated*/', true],
+ [
+ '/**
+ * @deprecated since 8.0.0
+ */',
+ true,
+ ],
+ ];
+ }
+
+ #[DataProvider('deprecatedDocCommentProvider')]
+ public function testIsDeprecatedByDocComment(string|null $docComment, bool $isDeprecated): void
+ {
+ $reflection = $this->createMock(ReflectionClass::class);
+ $reflection
+ ->method('getDocComment')
+ ->willReturn($docComment);
+
+ self::assertSame($isDeprecated, DeprecatedHelper::isDeprecated($reflection));
+ }
+}
diff --git a/test/unit/Reflection/ReflectionClassConstantTest.php b/test/unit/Reflection/ReflectionClassConstantTest.php
index a244c3ce7..2513baa7f 100644
--- a/test/unit/Reflection/ReflectionClassConstantTest.php
+++ b/test/unit/Reflection/ReflectionClassConstantTest.php
@@ -236,9 +236,13 @@ public function testGetDeclaringAndImplementingClass(string $constantName, strin
}
/** @return list */
- public static function deprecatedDocCommentProvider(): array
+ public static function deprecatedProvider(): array
{
return [
+ [
+ '#[Deprecated]',
+ true,
+ ],
[
'/**
* @deprecated since 8.0
@@ -258,14 +262,14 @@ public static function deprecatedDocCommentProvider(): array
];
}
- #[DataProvider('deprecatedDocCommentProvider')]
- public function testIsDeprecated(string $docComment, bool $isDeprecated): void
+ #[DataProvider('deprecatedProvider')]
+ public function testIsDeprecated(string $deprecatedCode, bool $isDeprecated): void
{
$php = sprintf('astLocator()));
$classReflection = $reflector->reflectClass('Foo');
diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php
index 4dea2ba1d..7d14faf9b 100644
--- a/test/unit/Reflection/ReflectionClassTest.php
+++ b/test/unit/Reflection/ReflectionClassTest.php
@@ -24,6 +24,7 @@
use ReflectionMethod as CoreReflectionMethod;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
+use Roave\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter;
use Roave\BetterReflection\Reflection\Exception\CircularReference;
use Roave\BetterReflection\Reflection\Exception\NotAClassReflection;
use Roave\BetterReflection\Reflection\Exception\NotAnInterfaceReflection;
@@ -615,7 +616,7 @@ public function testGetProperties(): void
$properties = $classInfo->getProperties();
self::assertContainsOnlyInstancesOf(ReflectionProperty::class, $properties);
- self::assertCount(9, $properties);
+ self::assertCount(10, $properties);
}
public function testGetPropertiesForPureEnum(): void
@@ -637,6 +638,7 @@ public function testGetPropertiesForPureEnum(): void
self::assertTrue($property->isReadOnly());
self::assertFalse($property->isPromoted());
self::assertTrue($property->isDefault());
+ self::assertFalse($property->isDynamic());
// No value property for pure enum
self::assertArrayNotHasKey('value', $properties);
@@ -695,6 +697,7 @@ public function testGetPropertiesForBackedEnum(string $className, array $propert
self::assertTrue($property->isReadOnly(), $fullPropertyName);
self::assertFalse($property->isPromoted(), $fullPropertyName);
self::assertTrue($property->isDefault(), $fullPropertyName);
+ self::assertFalse($property->isDynamic(), $fullPropertyName);
self::assertSame($propertyType, $property->getType()->__toString(), $fullPropertyName);
}
}
@@ -724,20 +727,24 @@ class Foo
self::assertSame($expectedPropertiesNames, array_keys($properties));
}
- /** @return list, 1: int}> */
+ /** @return list, 1: int}> */
public static function getPropertiesWithFilterDataProvider(): array
{
return [
[CoreReflectionProperty::IS_STATIC, 3],
- [CoreReflectionProperty::IS_PUBLIC, 3],
+ [ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY, 1],
+ [ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY, 0],
+ [CoreReflectionProperty::IS_PUBLIC, 4],
[CoreReflectionProperty::IS_PROTECTED, 2],
[CoreReflectionProperty::IS_PRIVATE, 4],
[
CoreReflectionProperty::IS_STATIC |
+ ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY |
+ ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY |
CoreReflectionProperty::IS_PUBLIC |
CoreReflectionProperty::IS_PROTECTED |
CoreReflectionProperty::IS_PRIVATE,
- 9,
+ 10,
],
];
}
@@ -753,6 +760,54 @@ public function testGetPropertiesWithFilter(int $filter, int $count): void
self::assertCount($count, $classInfo->getImmediateProperties($filter));
}
+ /** @return list, 1: int}> */
+ public static function getPropertiesWithAsymetricVisibilityFilterDataProvider(): array
+ {
+ return [
+ [CoreReflectionProperty::IS_PUBLIC, 6],
+ [CoreReflectionProperty::IS_PROTECTED, 4],
+ [CoreReflectionProperty::IS_PRIVATE, 2],
+ [ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY, 4],
+ [ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY, 6],
+ [
+ CoreReflectionProperty::IS_PUBLIC |
+ ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY,
+ 10,
+ ],
+ ];
+ }
+
+ /** @param int-mask-of $filter */
+ #[DataProvider('getPropertiesWithAsymetricVisibilityFilterDataProvider')]
+ public function testGetPropertiesWithAsymetricVisibilityFilter(int $filter, int $count): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+
+ self::assertCount($count, $classInfo->getProperties($filter));
+ self::assertCount($count, $classInfo->getImmediateProperties($filter));
+ }
+
+ /** @return list, 1: int}> */
+ public static function getPropertiesWithVirtualFilterDataProvider(): array
+ {
+ return [
+ [0, 4],
+ [ReflectionPropertyAdapter::IS_VIRTUAL_COMPATIBILITY, 2],
+ ];
+ }
+
+ /** @param int-mask-of $filter */
+ #[DataProvider('getPropertiesWithVirtualFilterDataProvider')]
+ public function testGetPropertiesWithVirtualFilter(int $filter, int $count): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\PropertyHooks');
+
+ self::assertCount($count, $classInfo->getProperties($filter));
+ self::assertCount($count, $classInfo->getImmediateProperties($filter));
+ }
+
public function testGetPropertiesReturnsInheritedProperties(): void
{
$classInfo = (new DefaultReflector(new SingleFileSourceLocator(
@@ -2253,6 +2308,17 @@ public function testToString(): void
);
}
+ public function testToStringWithAsymetricVisibility(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+
+ self::assertStringMatchesFormat(
+ file_get_contents(__DIR__ . '/../Fixture/AsymetricVisibilityClassExport.txt'),
+ $classInfo->__toString(),
+ );
+ }
+
public function testGetStaticProperties(): void
{
$staticPropertiesFixtureFile = __DIR__ . '/../Fixture/StaticProperties.php';
@@ -2793,9 +2859,13 @@ public function __toString(): string
}
/** @return list */
- public static function deprecatedDocCommentProvider(): array
+ public static function deprecatedProvider(): array
{
return [
+ [
+ '#[Deprecated]',
+ true,
+ ],
[
'/**
* @deprecated since 8.0
@@ -2815,12 +2885,12 @@ public static function deprecatedDocCommentProvider(): array
];
}
- #[DataProvider('deprecatedDocCommentProvider')]
- public function testIsDeprecated(string $docComment, bool $isDeprecated): void
+ #[DataProvider('deprecatedProvider')]
+ public function testIsDeprecated(string $deprecatedCode, bool $isDeprecated): void
{
$php = sprintf('astLocator));
$classReflection = $reflector->reflectClass('Foo');
diff --git a/test/unit/Reflection/ReflectionFunctionAbstractTest.php b/test/unit/Reflection/ReflectionFunctionAbstractTest.php
index 89fee0763..ca50541d7 100644
--- a/test/unit/Reflection/ReflectionFunctionAbstractTest.php
+++ b/test/unit/Reflection/ReflectionFunctionAbstractTest.php
@@ -136,32 +136,46 @@ public function testIsClosureWithArrowFunction(): void
self::assertTrue($function->isClosure());
}
- #[DataProvider('nonDeprecatedProvider')]
- public function testIsDeprecated(string $comment): void
- {
- $php = sprintf('astLocator));
- $function = $reflector->reflectFunction('foo');
-
- self::assertFalse($function->isDeprecated());
- }
-
- /** @return list */
- public static function nonDeprecatedProvider(): array
+ /** @return list */
+ public static function deprecatedProvider(): array
{
return [
- [''],
+ [
+ '#[Deprecated]',
+ true,
+ ],
[
'/**
- * @deprecatedPolicy
+ * @deprecated since 8.0
*/',
+ true,
+ ],
+ [
+ '/**
+ * @deprecated
+ */',
+ true,
+ ],
+ [
+ '',
+ false,
],
];
}
+ #[DataProvider('deprecatedProvider')]
+ public function testIsDeprecated(string $deprecatedCode, bool $isDeprecated): void
+ {
+ $php = sprintf('astLocator));
+ $function = $reflector->reflectFunction('foo');
+
+ self::assertSame($isDeprecated, $function->isDeprecated());
+ }
+
public function testIsInternal(): void
{
$php = 'isReadOnly());
}
+ public function testIsFinal(): void
+ {
+ $classInfo = $this->reflector->reflectClass(ExampleClass::class);
+
+ $notReadOnlyProperty = $classInfo->getProperty('publicProperty');
+ self::assertFalse($notReadOnlyProperty->isFinal());
+
+ $finalPublicProperty = $classInfo->getProperty('finalPublicProperty');
+ self::assertTrue($finalPublicProperty->isFinal());
+ self::assertTrue($finalPublicProperty->isPublic());
+ }
+
+ public function testIsNotAbstract(): void
+ {
+ $classInfo = $this->reflector->reflectClass(ExampleClass::class);
+
+ $notAbstractProperty = $classInfo->getProperty('publicProperty');
+ self::assertFalse($notAbstractProperty->isAbstract());
+ }
+
public function testIsReadOnlyInReadOnlyClass(): void
{
$reflector = new DefaultReflector(new SingleFileSourceLocator(
@@ -221,7 +244,7 @@ public function testGetDocCommentReturnsNullWithNoComment(): void
self::assertNull($property->getDocComment());
}
- /** @return list */
+ /** @return list}> */
public static function modifierProvider(): array
{
return [
@@ -230,6 +253,7 @@ public static function modifierProvider(): array
['privateProperty', CoreReflectionProperty::IS_PRIVATE],
['publicStaticProperty', CoreReflectionProperty::IS_PUBLIC | CoreReflectionProperty::IS_STATIC],
['readOnlyProperty', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_READONLY],
+ ['finalPublicProperty', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
];
}
@@ -255,22 +279,29 @@ public function testIsPromoted(): void
self::assertSame('int|null', $promotedProperty->getType()->__toString());
self::assertFalse($promotedProperty->hasDefaultValue());
self::assertNull($promotedProperty->getDefaultValue());
- self::assertSame(52, $promotedProperty->getStartLine());
- self::assertSame(52, $promotedProperty->getEndLine());
+ self::assertSame(54, $promotedProperty->getStartLine());
+ self::assertSame(54, $promotedProperty->getEndLine());
self::assertSame(60, $promotedProperty->getStartColumn());
self::assertSame(95, $promotedProperty->getEndColumn());
self::assertSame('/** Some doccomment */', $promotedProperty->getDocComment());
}
- public function testIsDefault(): void
+ public function testIsDefaultAndIsDynamic(): void
{
$classInfo = $this->reflector->reflectClass(ExampleClass::class);
- self::assertTrue($classInfo->getProperty('publicProperty')->isDefault());
- self::assertTrue($classInfo->getProperty('publicStaticProperty')->isDefault());
+ $publicProperty = $classInfo->getProperty('publicProperty');
+
+ self::assertTrue($publicProperty->isDefault());
+ self::assertFalse($publicProperty->isDynamic());
+
+ $publicStaticProperty = $classInfo->getProperty('publicStaticProperty');
+
+ self::assertTrue($publicStaticProperty->isDefault());
+ self::assertFalse($publicStaticProperty->isDynamic());
}
- public function testIsDefaultWithRuntimeDeclaredProperty(): void
+ public function testIsDefaultAndIsDynamicWithRuntimeDeclaredProperty(): void
{
$classInfo = $this->reflector->reflectClass(ExampleClass::class);
$propertyPropertyNode = new PropertyItem('foo');
@@ -285,6 +316,7 @@ public function testIsDefaultWithRuntimeDeclaredProperty(): void
);
self::assertFalse($propertyNode->isDefault());
+ self::assertTrue($propertyNode->isDynamic());
}
public function testToString(): void
@@ -861,4 +893,190 @@ public function testWithImplementingClass(): void
self::assertCount(2, $cloneAttributes);
self::assertNotSame($attributes[0], $cloneAttributes[0]);
}
+
+ /** @return list}> */
+ public static function asymetricVisibilityModifierProvider(): array
+ {
+ return [
+ ['publicPublicSet', CoreReflectionProperty::IS_PUBLIC],
+ ['publicProtectedSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
+ ['publicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['protectedProtectedSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
+ ['protectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['privatePrivateSet', CoreReflectionProperty::IS_PRIVATE | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['promotedPublicPublicSet', CoreReflectionProperty::IS_PUBLIC],
+ ['promotedPublicProtectedSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
+ ['promotedPublicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['promotedProtectedProtectedSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
+ ['promotedProtectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['promotedPrivatePrivateSet', CoreReflectionProperty::IS_PRIVATE | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ];
+ }
+
+ /** @param non-empty-string $propertyName */
+ #[DataProvider('asymetricVisibilityModifierProvider')]
+ public function testGetAsymetricVisibilityModifiers(string $propertyName, int $expectedModifier): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $property = $classInfo->getProperty($propertyName);
+
+ self::assertSame($expectedModifier, $property->getModifiers());
+ }
+
+ public function testIsAbstract(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AbstractPropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('hook');
+ self::assertTrue($hookProperty->isAbstract());
+ }
+
+ public function testNoHooks(): void
+ {
+ $classInfo = $this->reflector->reflectClass(ExampleClass::class);
+ $property = $classInfo->getProperty('publicProperty');
+
+ self::assertFalse($property->hasHooks());
+ self::assertCount(0, $property->getHooks());
+ self::assertFalse($property->hasHook(ReflectionPropertyHookType::Get));
+ self::assertNull($property->getHook(ReflectionPropertyHookType::Set));
+ self::assertFalse($property->hasHook(ReflectionPropertyHookType::Get));
+ self::assertNull($property->getHook(ReflectionPropertyHookType::Set));
+ }
+
+ public function testReadOnlyHook(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\PropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('readOnlyHook');
+ self::assertTrue($hookProperty->isDefault());
+ self::assertTrue($hookProperty->isVirtual());
+ self::assertTrue($hookProperty->hasHooks());
+
+ self::assertTrue($hookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertFalse($hookProperty->hasHook(ReflectionPropertyHookType::Set));
+
+ $hooks = $hookProperty->getHooks();
+ self::assertCount(1, $hooks);
+ self::assertArrayHasKey('get', $hooks);
+ self::assertInstanceOf(ReflectionMethod::class, $hooks['get']);
+ self::assertSame('$readOnlyHook::get', $hooks['get']->getName());
+ self::assertSame($hooks['get'], $hookProperty->getHook(ReflectionPropertyHookType::Get));
+ }
+
+ public function testWriteOnlyHook(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\PropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('writeOnlyHook');
+ self::assertTrue($hookProperty->isDefault());
+ self::assertFalse($hookProperty->isVirtual());
+ self::assertTrue($hookProperty->hasHooks());
+
+ self::assertFalse($hookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertTrue($hookProperty->hasHook(ReflectionPropertyHookType::Set));
+
+ $hooks = $hookProperty->getHooks();
+ self::assertCount(1, $hooks);
+ self::assertArrayHasKey('set', $hooks);
+ self::assertInstanceOf(ReflectionMethod::class, $hooks['set']);
+ self::assertSame('$writeOnlyHook::set', $hooks['set']->getName());
+ self::assertSame($hooks['set'], $hookProperty->getHook(ReflectionPropertyHookType::Set));
+ }
+
+ public function testBothReadAndWriteHooks(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\PropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('readAndWriteHook');
+ self::assertTrue($hookProperty->isDefault());
+ self::assertFalse($hookProperty->isVirtual());
+ self::assertTrue($hookProperty->hasHooks());
+
+ self::assertTrue($hookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertTrue($hookProperty->hasHook(ReflectionPropertyHookType::Set));
+
+ $hooks = $hookProperty->getHooks();
+ self::assertCount(2, $hooks);
+
+ self::assertArrayHasKey('get', $hooks);
+ self::assertInstanceOf(ReflectionMethod::class, $hooks['get']);
+ self::assertSame('$readAndWriteHook::get', $hooks['get']->getName());
+ self::assertSame($hooks['get'], $hookProperty->getHook(ReflectionPropertyHookType::Get));
+
+ self::assertArrayHasKey('set', $hooks);
+ self::assertInstanceOf(ReflectionMethod::class, $hooks['set']);
+ self::assertSame('$readAndWriteHook::set', $hooks['set']->getName());
+ self::assertSame($hooks['set'], $hookProperty->getHook(ReflectionPropertyHookType::Set));
+ }
+
+ public function testHooksForAbstractProperty(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AbstractPropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('hook');
+
+ self::assertTrue($hookProperty->isDefault());
+ self::assertTrue($hookProperty->isVirtual());
+ self::assertTrue($hookProperty->hasHooks());
+
+ $hooks = $hookProperty->getHooks();
+ self::assertCount(1, $hooks);
+
+ self::assertArrayHasKey('get', $hooks);
+ self::assertInstanceOf(ReflectionMethod::class, $hooks['get']);
+ self::assertSame('$hook::get', $hooks['get']->getName());
+ self::assertSame($hooks['get'], $hookProperty->getHook(ReflectionPropertyHookType::Get));
+ }
+
+ public function testIsVirtualWhenSetHookIsStrange(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\PropertyHooks');
+
+ $hookProperty = $classInfo->getProperty('virtualBecauseOfStrangeSetHook');
+ self::assertTrue($hookProperty->isVirtual());
+ }
+
+ public function testExtendingHooks(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $getClassInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\GetPropertyHook');
+
+ $getHookProperty = $getClassInfo->getProperty('hook');
+ self::assertTrue($getHookProperty->isVirtual());
+ self::assertCount(1, $getHookProperty->getHooks());
+ self::assertTrue($getHookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertFalse($getHookProperty->hasHook(ReflectionPropertyHookType::Set));
+ self::assertSame('Roave\BetterReflectionTest\Fixture\GetPropertyHook', $getHookProperty->getHook(ReflectionPropertyHookType::Get)->getDeclaringClass()->getName());
+
+ $getAndSetClassInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\GetAndSetPropertyHook');
+
+ $getAndSetHookProperty = $getAndSetClassInfo->getProperty('hook');
+ self::assertFalse($getAndSetHookProperty->isVirtual());
+ self::assertCount(2, $getAndSetHookProperty->getHooks());
+ self::assertTrue($getAndSetHookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertTrue($getAndSetHookProperty->hasHook(ReflectionPropertyHookType::Set));
+ self::assertSame('Roave\BetterReflectionTest\Fixture\GetPropertyHook', $getAndSetHookProperty->getHook(ReflectionPropertyHookType::Get)->getDeclaringClass()->getName());
+ self::assertSame('Roave\BetterReflectionTest\Fixture\GetAndSetPropertyHook', $getAndSetHookProperty->getHook(ReflectionPropertyHookType::Set)->getDeclaringClass()->getName());
+ }
+
+ public function testUseHookFromTrait(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $getClassInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\UsePropertyHookFromTrait');
+
+ $hookProperty = $getClassInfo->getProperty('hook');
+ self::assertTrue($hookProperty->isVirtual());
+ self::assertCount(1, $hookProperty->getHooks());
+ self::assertTrue($hookProperty->hasHook(ReflectionPropertyHookType::Get));
+ self::assertFalse($hookProperty->hasHook(ReflectionPropertyHookType::Set));
+ self::assertSame('Roave\BetterReflectionTest\Fixture\PropertyHookTrait', $hookProperty->getHook(ReflectionPropertyHookType::Get)->getDeclaringClass()->getName());
+ }
}
diff --git a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php
index 664594c15..9ed9284db 100644
--- a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php
+++ b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php
@@ -131,6 +131,20 @@ static function (string $className): bool {
return false;
}
+ // Missing in JetBrains/phpstorm-stubs
+ if (
+ PHP_VERSION_ID >= 80400
+ && in_array($className, [
+ 'Deprecated',
+ 'Generator',
+ 'RequestParseBodyException',
+ 'RoundingMode',
+ 'StreamBucket',
+ ], true)
+ ) {
+ return false;
+ }
+
// Check only always enabled extensions
return in_array($reflection->getExtensionName(), self::EXTENSIONS, true);
},
@@ -153,6 +167,11 @@ public function testInternalClasses(string $className): void
self::assertSame($internalReflection->isInterface(), $class->isInterface());
self::assertSame($internalReflection->isTrait(), $class->isTrait());
+ if (PHP_VERSION_ID >= 80400 && $className === 'SplObjectStorage') {
+ // Needs fixes in JetBrains/phpstorm-stubs
+ return;
+ }
+
self::assertSameClassAttributes($internalReflection, $class);
}
@@ -284,6 +303,25 @@ public static function internalFunctionsProvider(): array
static function (string $functionName): bool {
$reflection = new CoreReflectionFunction($functionName);
+ // Missing in JetBrains/phpstorm-stubs
+ if (
+ PHP_VERSION_ID >= 80400
+ && in_array($functionName, [
+ 'array_all',
+ 'array_any',
+ 'array_find',
+ 'array_find_key',
+ 'die',
+ 'exit',
+ 'fpow',
+ 'http_clear_last_response_headers',
+ 'http_get_last_response_headers',
+ 'request_parse_body',
+ ], true)
+ ) {
+ return false;
+ }
+
// Check only always enabled extensions
return in_array($reflection->getExtensionName(), self::EXTENSIONS, true);
},
@@ -344,6 +382,17 @@ public static function internalConstantsProvider(): array
}
foreach ($extensionConstants as $constantName => $constantValue) {
+ // Missing in JetBrains/phpstorm-stubs
+ if (
+ PHP_VERSION_ID >= 80400
+ && in_array($constantName, [
+ 'PHP_OUTPUT_HANDLER_PROCESSED',
+ 'PHP_SBINDIR',
+ ], true)
+ ) {
+ continue;
+ }
+
$provider[] = [$constantName, $constantValue, $extensionName];
}
}
diff --git a/test/unit/SourceLocator/SourceStubber/ReflectionSourceStubberTest.php b/test/unit/SourceLocator/SourceStubber/ReflectionSourceStubberTest.php
index bc386299c..378bbc37b 100644
--- a/test/unit/SourceLocator/SourceStubber/ReflectionSourceStubberTest.php
+++ b/test/unit/SourceLocator/SourceStubber/ReflectionSourceStubberTest.php
@@ -10,14 +10,16 @@
use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\TestCase;
use ReflectionClass as CoreReflectionClass;
+use ReflectionEnum as CoreReflectionEnum;
+use ReflectionEnumBackedCase as CoreReflectionEnumBackedCase;
use ReflectionException;
use ReflectionFunction as CoreReflectionFunction;
use ReflectionMethod as CoreReflectionMethod;
use ReflectionParameter as CoreReflectionParameter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionType;
use Roave\BetterReflection\Reflection\ReflectionClass;
-use Roave\BetterReflection\Reflection\ReflectionClassConstant;
use Roave\BetterReflection\Reflection\ReflectionConstant;
+use Roave\BetterReflection\Reflection\ReflectionEnum;
use Roave\BetterReflection\Reflection\ReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionParameter;
use Roave\BetterReflection\Reflector\DefaultReflector;
@@ -41,6 +43,7 @@
use function array_filter;
use function array_map;
use function array_merge;
+use function enum_exists;
use function get_declared_classes;
use function get_declared_interfaces;
use function get_declared_traits;
@@ -278,7 +281,9 @@ public function testInternalClasses(string $className): void
self::assertTrue($class->isInternal());
self::assertFalse($class->isUserDefined());
- $internalReflection = new CoreReflectionClass($className);
+ $internalReflection = enum_exists($className, false)
+ ? new CoreReflectionEnum($className)
+ : new CoreReflectionClass($className);
self::assertSame($internalReflection->isInterface(), $class->isInterface());
self::assertSame($internalReflection->isTrait(), $class->isTrait());
@@ -308,7 +313,7 @@ private function assertSameInterfaces(CoreReflectionClass $original, ReflectionC
self::assertSame($originalInterfacesNames, $stubbedInterfacesNames);
}
- private function assertSameClassAttributes(CoreReflectionClass $original, ReflectionClass $stubbed): void
+ private function assertSameClassAttributes(CoreReflectionClass|CoreReflectionEnum $original, ReflectionClass|ReflectionEnum $stubbed): void
{
self::assertSame($original->getName(), $stubbed->getName());
@@ -320,16 +325,23 @@ private function assertSameClassAttributes(CoreReflectionClass $original, Reflec
}
$this->assertSameClassConstants($original, $stubbed);
+
+ if (! ($original instanceof CoreReflectionEnum && $stubbed instanceof ReflectionEnum)) {
+ return;
+ }
+
+ $this->assertSameEnumCases($original, $stubbed);
}
private function assertSameClassConstants(CoreReflectionClass $original, ReflectionClass $stubbed): void
{
- self::assertEquals(
- $original->getConstants(),
- array_map(static fn (ReflectionClassConstant $classConstant) => $classConstant->getValue(), $stubbed->getConstants()),
- );
+ // We don't check getConstants() method because native reflection returns constants and enum cases together
foreach ($original->getReflectionConstants() as $originalConstant) {
+ if ($originalConstant->isEnumCase()) {
+ continue;
+ }
+
if (
! method_exists($originalConstant, 'hasType')
|| ! method_exists($originalConstant, 'getType')
@@ -339,6 +351,7 @@ private function assertSameClassConstants(CoreReflectionClass $original, Reflect
$stubbedConstant = $stubbed->getConstant($originalConstant->getName());
+ self::assertNotNull($stubbedConstant);
self::assertSame($originalConstant->hasType(), $stubbedConstant->hasType());
self::assertSame(
(string) $originalConstant->getType(),
@@ -348,6 +361,25 @@ private function assertSameClassConstants(CoreReflectionClass $original, Reflect
}
}
+ private function assertSameEnumCases(CoreReflectionEnum $original, ReflectionEnum $stubbed): void
+ {
+ foreach ($original->getCases() as $originalEnumCase) {
+ $stubbedEnumCase = $stubbed->getCase($originalEnumCase->getName());
+
+ self::assertNotNull($stubbedEnumCase);
+
+ if (! ($originalEnumCase instanceof CoreReflectionEnumBackedCase)) {
+ continue;
+ }
+
+ self::assertSame(
+ $originalEnumCase->getBackingValue(),
+ $stubbedEnumCase->getValue(),
+ $original->getName() . '::' . $originalEnumCase->getName(),
+ );
+ }
+ }
+
private function assertSameMethodAttributes(CoreReflectionMethod $original, ReflectionMethod $stubbed): void
{
$originalParameterNames = array_map(
@@ -423,7 +455,7 @@ public static function internalFunctionsProvider(): array
static function (string $functionName): bool {
$reflection = new CoreReflectionFunction($functionName);
- return $reflection->isInternal();
+ return $reflection->isInternal() && ! $reflection->isDeprecated();
},
),
);