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 0b259f0cc..1b91b9c2b 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..e2f9ca57c 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -144,11 +144,15 @@
+
+
+
+
@@ -201,14 +205,22 @@
+
+
+
+
+
+
+
+
diff --git a/src/Reflection/Adapter/Exception/NotImplementedBecauseItTriggersAutoloading.php b/src/Reflection/Adapter/Exception/NotImplementedBecauseItTriggersAutoloading.php
new file mode 100644
index 000000000..1f8c3de6e
--- /dev/null
+++ b/src/Reflection/Adapter/Exception/NotImplementedBecauseItTriggersAutoloading.php
@@ -0,0 +1,13 @@
+ */
final class ReflectionAttribute extends CoreReflectionAttribute
{
public function __construct(private BetterReflectionAttribute $betterReflectionAttribute)
{
+ unset($this->name);
}
/** @psalm-mutation-free */
@@ -44,9 +48,10 @@ public function getArguments(): array
return $this->betterReflectionAttribute->getArguments();
}
+ /** @return never */
public function newInstance(): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
/** @return non-empty-string */
@@ -54,4 +59,13 @@ public function __toString(): string
{
return $this->betterReflectionAttribute->__toString();
}
+
+ public function __get(string $name): mixed
+ {
+ if ($name === 'name') {
+ return $this->betterReflectionAttribute->getName();
+ }
+
+ throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
+ }
}
diff --git a/src/Reflection/Adapter/ReflectionClass.php b/src/Reflection/Adapter/ReflectionClass.php
index 64bbe2ed5..b71c3613f 100644
--- a/src/Reflection/Adapter/ReflectionClass.php
+++ b/src/Reflection/Adapter/ReflectionClass.php
@@ -33,6 +33,20 @@
*/
final class ReflectionClass extends CoreReflectionClass
{
+ /**
+ * @internal
+ *
+ * @see CoreReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE_COMPATIBILITY
+ */
+ public const SKIP_INITIALIZATION_ON_SERIALIZE_COMPATIBILITY = 8;
+
+ /**
+ * @internal
+ *
+ * @see CoreReflectionClass::SKIP_DESTRUCTOR
+ */
+ public const SKIP_DESTRUCTOR_COMPATIBILITY = 16;
+
public function __construct(private BetterReflectionClass|BetterReflectionEnum $betterReflectionClass)
{
unset($this->name);
@@ -435,19 +449,78 @@ public function isInstance(object $object): bool
return $this->betterReflectionClass->isInstance($object);
}
+ /** @return never */
public function newInstance(mixed ...$args): self
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceWithoutConstructor(): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceArgs(array|null $args = null): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function initializeLazyObject(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
/** @psalm-mutation-free */
@@ -566,7 +639,11 @@ public function implementsInterface(CoreReflectionClass|string $interface): bool
return $this->betterReflectionClass->implementsInterface($realInterfaceName);
}
- /** @psalm-mutation-free */
+ /**
+ * @return never
+ *
+ * @psalm-mutation-free
+ */
public function getExtension(): CoreReflectionExtension|null
{
throw new Exception\NotImplemented('Not implemented');
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..66c4151e7 100644
--- a/src/Reflection/Adapter/ReflectionEnum.php
+++ b/src/Reflection/Adapter/ReflectionEnum.php
@@ -361,19 +361,78 @@ public function isInstance(object $object): bool
return $this->betterReflectionEnum->isInstance($object);
}
+ /** @return never */
public function newInstance(mixed ...$args): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceWithoutConstructor(): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceArgs(array|null $args = null): object
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function initializeLazyObject(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
public function getParentClass(): ReflectionClass|false
@@ -449,6 +508,7 @@ public function implementsInterface(CoreReflectionClass|string $interface): bool
return $this->betterReflectionEnum->implementsInterface($realInterfaceName);
}
+ /** @return never */
public function getExtension(): CoreReflectionExtension|null
{
throw new Exception\NotImplemented('Not implemented');
diff --git a/src/Reflection/Adapter/ReflectionEnumBackedCase.php b/src/Reflection/Adapter/ReflectionEnumBackedCase.php
index 47e3aebcd..b8488c03f 100644
--- a/src/Reflection/Adapter/ReflectionEnumBackedCase.php
+++ b/src/Reflection/Adapter/ReflectionEnumBackedCase.php
@@ -45,9 +45,10 @@ public function getType(): null
return null;
}
+ /** @return never */
public function getValue(): UnitEnum
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
public function isPublic(): bool
@@ -129,6 +130,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..520c7c975 100644
--- a/src/Reflection/Adapter/ReflectionEnumUnitCase.php
+++ b/src/Reflection/Adapter/ReflectionEnumUnitCase.php
@@ -45,9 +45,10 @@ public function getType(): null
return null;
}
+ /** @return never */
public function getValue(): UnitEnum
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
public function isPublic(): bool
@@ -124,6 +125,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/ReflectionFunction.php b/src/Reflection/Adapter/ReflectionFunction.php
index 94a6b70a5..92536d85b 100644
--- a/src/Reflection/Adapter/ReflectionFunction.php
+++ b/src/Reflection/Adapter/ReflectionFunction.php
@@ -244,7 +244,11 @@ public function getClosure(): Closure
return $this->betterReflectionFunction->getClosure();
}
- /** @return mixed[] */
+ /**
+ * @return never
+ *
+ * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
+ */
public function getClosureUsedVariables(): array
{
throw new Exception\NotImplemented('Not implemented');
diff --git a/src/Reflection/Adapter/ReflectionMethod.php b/src/Reflection/Adapter/ReflectionMethod.php
index 2d2caf45a..b208b2463 100644
--- a/src/Reflection/Adapter/ReflectionMethod.php
+++ b/src/Reflection/Adapter/ReflectionMethod.php
@@ -401,7 +401,11 @@ public function getTentativeReturnType(): CoreReflectionType|null
return ReflectionType::fromTypeOrNull($this->betterReflectionMethod->getTentativeReturnType());
}
- /** @return mixed[] */
+ /**
+ * @return never
+ *
+ * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
+ */
public function getClosureUsedVariables(): array
{
throw new Exception\NotImplemented('Not implemented');
diff --git a/src/Reflection/Adapter/ReflectionNamedType.php b/src/Reflection/Adapter/ReflectionNamedType.php
index 80d97e3de..9bbc3fce9 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 = false)
{
+ if ($type instanceof BetterReflectionNamedType) {
+ $nameType = $type->getName();
+ $this->nameType = $nameType;
+ $this->isBuiltin = self::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 static 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..24722f5c2 100644
--- a/src/Reflection/Adapter/ReflectionObject.php
+++ b/src/Reflection/Adapter/ReflectionObject.php
@@ -381,19 +381,78 @@ public function isInstance(object $object): bool
return $this->betterReflectionObject->isInstance($object);
}
+ /** @return never */
public function newInstance(mixed ...$args): ReflectionObject
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceWithoutConstructor(): ReflectionObject
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
+ /** @return never */
public function newInstanceArgs(array|null $args = null): ReflectionObject
{
- throw new Exception\NotImplemented('Not implemented');
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyGhost(callable $initializer, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /**
+ * @param int-mask-of $options
+ *
+ * @return never
+ */
+ public function newLazyProxy(callable $factory, int $options = 0): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function markLazyObjectAsInitialized(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function getLazyInitializer(object $object): callable|null
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function initializeLazyObject(object $object): object
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function isUninitializedLazyObject(object $object): bool
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @param int-mask-of $options */
+ public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
}
/** @psalm-mutation-free */
@@ -508,7 +567,11 @@ public function implementsInterface(CoreReflectionClass|string $interface): bool
return $this->betterReflectionObject->implementsInterface($realInterfaceName);
}
- /** @psalm-mutation-free */
+ /**
+ * @return never
+ *
+ * @psalm-mutation-free
+ */
public function getExtension(): CoreReflectionExtension|null
{
throw new Exception\NotImplemented('Not implemented');
diff --git a/src/Reflection/Adapter/ReflectionProperty.php b/src/Reflection/Adapter/ReflectionProperty.php
index 6f009056e..d64096f7a 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,41 @@
/** @psalm-suppress PropertyNotSetInConstructor */
final class ReflectionProperty extends CoreReflectionProperty
{
+ /**
+ * @internal
+ *
+ * @see CoreReflectionProperty::IS_FINAL
+ */
+ public const IS_FINAL_COMPATIBILITY = 32;
+
+ /**
+ * @internal
+ *
+ * @see CoreReflectionProperty::IS_ABSTRACT
+ */
+ public const IS_ABSTRACT_COMPATIBILITY = 64;
+
+ /**
+ * @internal
+ *
+ * @see CoreReflectionProperty::IS_VIRTUAL
+ */
+ public const IS_VIRTUAL_COMPATIBILITY = 512;
+
+ /**
+ * @internal
+ *
+ * @see CoreReflectionProperty::IS_PROTECTED_SET
+ */
+ public const IS_PROTECTED_SET_COMPATIBILITY = 2048;
+
+ /**
+ * @internal
+ *
+ * @see CoreReflectionProperty::IS_PRIVATE_SET
+ */
+ public const IS_PRIVATE_SET_COMPATIBILITY = 4096;
+
public function __construct(private BetterReflectionProperty $betterReflectionProperty)
{
unset($this->name);
@@ -66,6 +105,22 @@ public function setValue(mixed $objectOrValue, mixed $value = null): void
}
}
+ public function setRawValueWithoutLazyInitialization(object $object, mixed $value): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ /** @return never */
+ public function isLazy(object $object): bool
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ public function skipLazyInitialization(object $object): never
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
/** @psalm-mutation-free */
public function hasType(): bool
{
@@ -90,24 +145,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 +275,72 @@ 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');
+ }
+
+ return $this->getType();
+ }
+
+ /** @return never */
+ public function getRawValue(object $object): mixed
+ {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ public function setRawValue(object $object, mixed $value): void
+ {
+ if ($this->hasHooks()) {
+ throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
+ }
+
+ $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..54d598034 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);
}
/**
@@ -1199,7 +1203,7 @@ public function isDeprecated(): bool
*/
public function isAbstract(): bool
{
- return ($this->modifiers & CoreReflectionClass::IS_EXPLICIT_ABSTRACT) === CoreReflectionClass::IS_EXPLICIT_ABSTRACT;
+ return (bool) ($this->modifiers & CoreReflectionClass::IS_EXPLICIT_ABSTRACT);
}
/**
@@ -1211,12 +1215,12 @@ public function isFinal(): bool
return true;
}
- return ($this->modifiers & CoreReflectionClass::IS_FINAL) === CoreReflectionClass::IS_FINAL;
+ return (bool) ($this->modifiers & CoreReflectionClass::IS_FINAL);
}
public function isReadOnly(): bool
{
- return ($this->modifiers & CoreReflectionClass::IS_READONLY) === CoreReflectionClass::IS_READONLY;
+ return (bool) ($this->modifiers & CoreReflectionClass::IS_READONLY);
}
/**
diff --git a/src/Reflection/ReflectionClassConstant.php b/src/Reflection/ReflectionClassConstant.php
index 33b09acaf..73efcf316 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;
@@ -174,7 +174,7 @@ public function getValue(): mixed
*/
public function isPublic(): bool
{
- return ($this->modifiers & CoreReflectionClassConstant::IS_PUBLIC) === CoreReflectionClassConstant::IS_PUBLIC;
+ return (bool) ($this->modifiers & CoreReflectionClassConstant::IS_PUBLIC);
}
/**
@@ -191,12 +191,12 @@ public function isPrivate(): bool
*/
public function isProtected(): bool
{
- return ($this->modifiers & CoreReflectionClassConstant::IS_PROTECTED) === CoreReflectionClassConstant::IS_PROTECTED;
+ return (bool) ($this->modifiers & CoreReflectionClassConstant::IS_PROTECTED);
}
public function isFinal(): bool
{
- return ($this->modifiers & ReflectionClassConstantAdapter::IS_FINAL) === ReflectionClassConstantAdapter::IS_FINAL;
+ return (bool) ($this->modifiers & ReflectionClassConstantAdapter::IS_FINAL);
}
/**
@@ -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..377ff5af1 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,
@@ -287,7 +292,7 @@ public function isClosure(): bool
*/
public function isAbstract(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_ABSTRACT) === CoreReflectionMethod::IS_ABSTRACT
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_ABSTRACT)
|| $this->declaringClass->isInterface();
}
@@ -296,7 +301,7 @@ public function isAbstract(): bool
*/
public function isFinal(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_FINAL) === CoreReflectionMethod::IS_FINAL;
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_FINAL);
}
/**
@@ -304,7 +309,7 @@ public function isFinal(): bool
*/
public function isPrivate(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_PRIVATE) === CoreReflectionMethod::IS_PRIVATE;
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_PRIVATE);
}
/**
@@ -312,7 +317,7 @@ public function isPrivate(): bool
*/
public function isProtected(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_PROTECTED) === CoreReflectionMethod::IS_PROTECTED;
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_PROTECTED);
}
/**
@@ -320,7 +325,7 @@ public function isProtected(): bool
*/
public function isPublic(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_PUBLIC) === CoreReflectionMethod::IS_PUBLIC;
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_PUBLIC);
}
/**
@@ -328,7 +333,7 @@ public function isPublic(): bool
*/
public function isStatic(): bool
{
- return ($this->modifiers & CoreReflectionMethod::IS_STATIC) === CoreReflectionMethod::IS_STATIC;
+ return (bool) ($this->modifiers & CoreReflectionMethod::IS_STATIC);
}
/**
diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php
index dd56af161..ecadb99c7 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;
@@ -32,6 +35,7 @@
use function array_map;
use function assert;
+use function count;
use function func_num_args;
use function is_object;
use function sprintf;
@@ -68,6 +72,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 +98,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 +233,11 @@ public function isDefault(): bool
return $this->declaredAtCompileTime;
}
+ public function isDynamic(): bool
+ {
+ return ! $this->isDefault();
+ }
+
/**
* Get the core-reflection-compatible modifier values.
*
@@ -220,7 +245,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;
}
/**
@@ -238,7 +267,12 @@ public function getName(): string
*/
public function isPrivate(): bool
{
- return ($this->modifiers & CoreReflectionProperty::IS_PRIVATE) === CoreReflectionProperty::IS_PRIVATE;
+ return (bool) ($this->modifiers & CoreReflectionProperty::IS_PRIVATE);
+ }
+
+ public function isPrivateSet(): bool
+ {
+ return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY);
}
/**
@@ -246,7 +280,12 @@ public function isPrivate(): bool
*/
public function isProtected(): bool
{
- return ($this->modifiers & CoreReflectionProperty::IS_PROTECTED) === CoreReflectionProperty::IS_PROTECTED;
+ return (bool) ($this->modifiers & CoreReflectionProperty::IS_PROTECTED);
+ }
+
+ public function isProtectedSet(): bool
+ {
+ return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY);
}
/**
@@ -254,7 +293,7 @@ public function isProtected(): bool
*/
public function isPublic(): bool
{
- return ($this->modifiers & CoreReflectionProperty::IS_PUBLIC) === CoreReflectionProperty::IS_PUBLIC;
+ return (bool) ($this->modifiers & CoreReflectionProperty::IS_PUBLIC);
}
/**
@@ -262,7 +301,17 @@ public function isPublic(): bool
*/
public function isStatic(): bool
{
- return ($this->modifiers & CoreReflectionProperty::IS_STATIC) === CoreReflectionProperty::IS_STATIC;
+ return (bool) ($this->modifiers & CoreReflectionProperty::IS_STATIC);
+ }
+
+ public function isFinal(): bool
+ {
+ return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY);
+ }
+
+ public function isAbstract(): bool
+ {
+ return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY);
}
public function isPromoted(): bool
@@ -293,7 +342,7 @@ public function isInitialized(object|null $object = null): bool
public function isReadOnly(): bool
{
- return ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY) === ReflectionPropertyAdapter::IS_READONLY
+ return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY)
|| $this->getDeclaringClass()->isReadOnly();
}
@@ -353,7 +402,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 +597,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 +671,172 @@ 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;
+ $getHook = null;
+
+ foreach ($node->hooks as $hook) {
+ if ($hook->name->name === 'set') {
+ $setHook = $hook;
+ } elseif ($hook->name->name === 'get') {
+ $getHook = $hook;
+ }
+ }
+
+ if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnSetHook($setHook)) {
+ return false;
+ }
+
+ if ($getHook === null) {
+ return true;
+ }
+
+ return $this->computeImmediateVirtualBasedOnGetHook($node, $getHook);
+ }
+
+ private function computeImmediateVirtualBasedOnGetHook(PropertyNode $node, Node\PropertyHook $getHook): bool
+ {
+ $getHookBody = $getHook->getStmts();
+
+ // Abstract property or property in interface
+ if ($getHookBody === null) {
+ return true;
+ }
+
+ if (! $node->isPublic()) {
+ return true;
+ }
+
+ $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch);
+ $traverser = new NodeTraverser($visitor);
+ $traverser->traverse($getHookBody);
+
+ foreach ($visitor->getFoundNodes() as $propertyFetchNode) {
+ assert($propertyFetchNode instanceof Node\Expr\PropertyFetch);
+
+ if (
+ $propertyFetchNode->var instanceof Node\Expr\Variable
+ && $propertyFetchNode->var->name === 'this'
+ && $propertyFetchNode->name instanceof Node\Identifier
+ && $propertyFetchNode->name->name === $this->name
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private function computeImmediateVirtualBasedOnSetHook(Node\PropertyHook $setHook): bool
+ {
+ $setHookBody = $setHook->getStmts();
+
+ // Abstract property or property in interface
+ if ($setHookBody === null) {
+ return true;
+ }
+
+ // Short syntax
+ if (count($setHookBody) === 1 && $setHookBody[0] instanceof Node\Stmt\Return_) {
+ return false;
+ }
+
+ $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() ?? true;
+ }
+
+ /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
+ private function createCachedHooks(): array
+ {
+ $hooks = $this->immediateHooks;
+
+ // Just optimization - we don't need to check parent property when both hooks are defined in this class
+ 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..4dceffad8
--- /dev/null
+++ b/src/Reflection/ReflectionPropertyHookType.php
@@ -0,0 +1,31 @@
+ self::Get,
+ CoreReflectionPropertyHookType::Set => self::Set,
+ };
+ }
+}
diff --git a/src/Reflection/StringCast/ReflectionClassConstantStringCast.php b/src/Reflection/StringCast/ReflectionClassConstantStringCast.php
index bf922c723..591c6392b 100644
--- a/src/Reflection/StringCast/ReflectionClassConstantStringCast.php
+++ b/src/Reflection/StringCast/ReflectionClassConstantStringCast.php
@@ -18,13 +18,14 @@ final class ReflectionClassConstantStringCast
*
* @psalm-pure
*/
- public static function toString(ReflectionClassConstant $constantReflection): string
+ public static function toString(ReflectionClassConstant $constantReflection, bool $indentDocComment = true): string
{
/** @psalm-var scalar|array $value */
$value = $constantReflection->getValue();
return sprintf(
- "Constant [ %s%s %s %s ] { %s }\n",
+ "%sConstant [ %s%s %s %s ] { %s }\n",
+ ReflectionStringCastHelper::docCommentToString($constantReflection, $indentDocComment),
$constantReflection->isFinal() ? 'final ' : '',
self::visibilityToString($constantReflection),
gettype($value),
diff --git a/src/Reflection/StringCast/ReflectionClassStringCast.php b/src/Reflection/StringCast/ReflectionClassStringCast.php
index 961b684fc..01b53184b 100644
--- a/src/Reflection/StringCast/ReflectionClassStringCast.php
+++ b/src/Reflection/StringCast/ReflectionClassStringCast.php
@@ -160,8 +160,8 @@ private static function constantsToString(array $constants, array $enumCases): s
return '';
}
- $items = array_map(static fn (ReflectionEnumCase $enumCaseReflection): string => trim(ReflectionEnumCaseStringCast::toString($enumCaseReflection)), $enumCases)
- + array_map(static fn (ReflectionClassConstant $constantReflection): string => trim(ReflectionClassConstantStringCast::toString($constantReflection)), $constants);
+ $items = array_map(static fn (ReflectionEnumCase $enumCaseReflection): string => trim(ReflectionEnumCaseStringCast::toString($enumCaseReflection, indentDocComment: false)), $enumCases)
+ + array_map(static fn (ReflectionClassConstant $constantReflection): string => trim(ReflectionClassConstantStringCast::toString($constantReflection, indentDocComment: false)), $constants);
return self::itemsToString($items);
}
@@ -177,7 +177,7 @@ private static function propertiesToString(array $properties): string
return '';
}
- return self::itemsToString(array_map(static fn (ReflectionProperty $propertyReflection): string => ReflectionPropertyStringCast::toString($propertyReflection), $properties));
+ return self::itemsToString(array_map(static fn (ReflectionProperty $propertyReflection): string => ReflectionPropertyStringCast::toString($propertyReflection, indentDocComment: false), $properties));
}
/**
diff --git a/src/Reflection/StringCast/ReflectionEnumCaseStringCast.php b/src/Reflection/StringCast/ReflectionEnumCaseStringCast.php
index 6a9851e66..6e5257865 100644
--- a/src/Reflection/StringCast/ReflectionEnumCaseStringCast.php
+++ b/src/Reflection/StringCast/ReflectionEnumCaseStringCast.php
@@ -17,7 +17,7 @@ final class ReflectionEnumCaseStringCast
*
* @psalm-pure
*/
- public static function toString(ReflectionEnumCase $enumCaseReflection): string
+ public static function toString(ReflectionEnumCase $enumCaseReflection, bool $indentDocComment = true): string
{
$enumReflection = $enumCaseReflection->getDeclaringEnum();
@@ -25,7 +25,8 @@ public static function toString(ReflectionEnumCase $enumCaseReflection): string
$type = $enumReflection->isBacked() ? gettype($value) : $enumReflection->getName();
return sprintf(
- "Constant [ public %s %s ] { %s }\n",
+ "%sConstant [ public %s %s ] { %s }\n",
+ ReflectionStringCastHelper::docCommentToString($enumCaseReflection, $indentDocComment),
$type,
$enumCaseReflection->getName(),
$value,
diff --git a/src/Reflection/StringCast/ReflectionPropertyStringCast.php b/src/Reflection/StringCast/ReflectionPropertyStringCast.php
index 657ce8e2d..f3cc56318 100644
--- a/src/Reflection/StringCast/ReflectionPropertyStringCast.php
+++ b/src/Reflection/StringCast/ReflectionPropertyStringCast.php
@@ -16,7 +16,7 @@ final class ReflectionPropertyStringCast
*
* @psalm-pure
*/
- public static function toString(ReflectionProperty $propertyReflection): string
+ public static function toString(ReflectionProperty $propertyReflection, bool $indentDocComment = true): string
{
$stateModifier = '';
@@ -27,7 +27,8 @@ public static function toString(ReflectionProperty $propertyReflection): string
$type = $propertyReflection->getType();
return sprintf(
- 'Property [%s %s%s%s%s $%s ]',
+ '%sProperty [%s %s%s%s%s $%s ]',
+ ReflectionStringCastHelper::docCommentToString($propertyReflection, $indentDocComment),
$stateModifier,
self::visibilityToString($propertyReflection),
$propertyReflection->isStatic() ? ' static' : '',
diff --git a/src/Reflection/StringCast/ReflectionStringCastHelper.php b/src/Reflection/StringCast/ReflectionStringCastHelper.php
new file mode 100644
index 000000000..e441d71d8
--- /dev/null
+++ b/src/Reflection/StringCast/ReflectionStringCastHelper.php
@@ -0,0 +1,27 @@
+getDocComment();
+
+ if ($docComment === null) {
+ return '';
+ }
+
+ return ($indent ? preg_replace('/(\n)(?!\n)/', '\1 ', $docComment) : $docComment) . "\n";
+ }
+}
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/Enums.php b/test/unit/Fixture/Enums.php
index f151b0f2a..7818381d6 100644
--- a/test/unit/Fixture/Enums.php
+++ b/test/unit/Fixture/Enums.php
@@ -4,6 +4,10 @@
enum PureEnum implements InterfaceForEnum
{
+
+ /**
+ * One
+ */
case ONE;
case TWO;
case THREE;
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..2f3e8375a 100644
--- a/test/unit/Fixture/ExampleClassExport.txt
+++ b/test/unit/Fixture/ExampleClassExport.txt
@@ -1,8 +1,11 @@
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 }
+ /**
+ * This comment for constant should be used.
+ */
Constant [ public integer MY_CONST_2 ] { 234 }
Constant [ public integer MY_CONST_3 ] { 345 }
Constant [ protected integer MY_CONST_4 ] { 456 }
@@ -21,18 +24,29 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] {
- Static methods [0] {
}
- - Properties [6] {
+ - Properties [7] {
+ /**
+ * @var int|float|\stdClass
+ */
Property [ private $privateProperty ]
+ /**
+ * @var bool|bool[]|bool[][]
+ */
Property [ protected $protectedProperty ]
+ /**
+ * @var string
+ */
Property [ public $publicProperty ]
Property [ public readonly int $readOnlyProperty ]
+ Property [ public int $finalPublicProperty ]
+ /** Some doccomment */
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 +56,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..81359ce0f
--- /dev/null
+++ b/test/unit/Fixture/PropertyHooks.php
@@ -0,0 +1,130 @@
+writeOnlyHook = $value;
+ }
+ }
+
+ public string $readAndWriteHook {
+ get {
+ $this->readAndWriteHook;
+ }
+ set (string $value) {
+ $this->readAndWriteHook = $value;
+ }
+ }
+}
+
+abstract class ToBeVirtualOrNotToBeVirtualThatIsTheQuestion
+{
+ public string $notVirtualBecauseNoHooks = 'string';
+
+ public string $notVirtualBecauseOfPublicVisibilityAndThePropertyIsUsedInGet {
+ get {
+ return strtoupper($this->notVirtualBecauseOfPublicVisibilityAndThePropertyIsUsedInGet);
+ }
+ }
+
+ protected string $virtualBecauseOfNotPublicVisibilityAndNoSet {
+ get {
+ return strtoupper($this->virtualBecauseOfNotPublicVisibilityAndNoSet);
+ }
+ }
+
+ public string $notVirtualBecauseOfShortSyntax {
+ set => strtolower($value);
+ }
+
+ public string $virtualBecauseThePropertyIsNotUsedInGet {
+ get => 'value';
+ }
+
+ public string $virtualBecauseSetWorksWithDifferentProperty {
+ set (string $value) {
+ $this->differentProperty = $value;
+ }
+ }
+
+ public string $notVirtualBecauseIsPublicSoTheSetWithDifferentPropertyIsNotRelevant {
+ set {
+ $this->differentProperty = $value;
+ }
+ get {
+ return $this->notVirtualBecauseIsPublicSoTheSetWithDifferentPropertyIsNotRelevant;
+ }
+ }
+
+ abstract public string $virtualBecauseGetAndSetAbstract {
+ get;
+ set;
+ }
+
+ abstract public string $notVirtualBecauseSetIsNotAbstract {
+ get;
+ set (string $value) {
+ $this->notVirtualBecauseSetIsNotAbstract = $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;
+}
+
+interface InterfacePropertyHooks
+{
+ public string $readOnlyHook { get; }
+
+ public string $writeOnlyHook { set; }
+
+ public string $readAndWriteHook { get; set; }
+}
diff --git a/test/unit/Fixture/StringCastBackedEnum.php b/test/unit/Fixture/StringCastBackedEnum.php
index b52e9ddec..7b9350325 100644
--- a/test/unit/Fixture/StringCastBackedEnum.php
+++ b/test/unit/Fixture/StringCastBackedEnum.php
@@ -7,4 +7,9 @@ enum StringCastBackedEnum: string
case ENUM_CASE = 'string';
const CONSTANT = 'constant';
+
+ /**
+ * Something
+ */
+ case ENUM_CASE_WITH_DOC_COMMENT = 'something';
}
diff --git a/test/unit/Fixture/StringCastBackedEnumExpected.txt b/test/unit/Fixture/StringCastBackedEnumExpected.txt
index ddfd50381..80cd28652 100644
--- a/test/unit/Fixture/StringCastBackedEnumExpected.txt
+++ b/test/unit/Fixture/StringCastBackedEnumExpected.txt
@@ -1,8 +1,12 @@
Class [ final class Roave\BetterReflectionTest\Fixture\StringCastBackedEnum implements UnitEnum, BackedEnum ] {
- @@ %s/Fixture/StringCastBackedEnum.php 5-10
+ @@ %s/Fixture/StringCastBackedEnum.php 5-15
- - Constants [2] {
+ - Constants [3] {
Constant [ public string ENUM_CASE ] { string }
+ /**
+ * Something
+ */
+ Constant [ public string ENUM_CASE_WITH_DOC_COMMENT ] { something }
Constant [ public string CONSTANT ] { constant }
}
diff --git a/test/unit/Fixture/StringCastClass.php b/test/unit/Fixture/StringCastClass.php
index 32965af28..80e8c2a58 100644
--- a/test/unit/Fixture/StringCastClass.php
+++ b/test/unit/Fixture/StringCastClass.php
@@ -30,6 +30,11 @@ abstract class StringCastClass extends StringCastClassParent implements StringCa
private const PRIVATE_CONSTANT = 'string';
const NO_VISIBILITY_CONSTANT = [];
+ /**
+ * @var string
+ */
+ const WITH_DOC_COMMENT_CONSTANT = 'string';
+
private $privateProperty = 'string';
protected $protectedProperty = 0;
public $publicProperty = true;
@@ -41,6 +46,11 @@ abstract class StringCastClass extends StringCastClassParent implements StringCa
public readonly int $readOnlyProperty;
+ /**
+ * @var string
+ */
+ public $propertyWithDocComment;
+
public function __construct()
{
}
diff --git a/test/unit/Fixture/StringCastClassConstants.php b/test/unit/Fixture/StringCastClassConstants.php
index 0daa29362..5f108439d 100644
--- a/test/unit/Fixture/StringCastClassConstants.php
+++ b/test/unit/Fixture/StringCastClassConstants.php
@@ -9,4 +9,9 @@ class StringCastConstants
private const PRIVATE_CONSTANT = 'string';
const NO_VISIBILITY_CONSTANT = [];
final public const FINAL_CONSTANT = 'final';
+
+ /**
+ * @var string
+ */
+ const WITH_DOC_COMMENT_CONSTANT = 'string';
}
diff --git a/test/unit/Fixture/StringCastClassExpected.txt b/test/unit/Fixture/StringCastClassExpected.txt
index aaf1172fa..eedc699a6 100644
--- a/test/unit/Fixture/StringCastClassExpected.txt
+++ b/test/unit/Fixture/StringCastClassExpected.txt
@@ -1,11 +1,15 @@
Class [ abstract class Roave\BetterReflectionTest\Fixture\StringCastClass extends Roave\BetterReflectionTest\Fixture\StringCastClassParent implements Roave\BetterReflectionTest\Fixture\StringCastClassInterface2, Roave\BetterReflectionTest\Fixture\StringCastClassInterface ] {
- @@ %s/Fixture/StringCastClass.php 26-89
+ @@ %s/Fixture/StringCastClass.php 26-99
- - Constants [4] {
+ - Constants [5] {
Constant [ public boolean PUBLIC_CONSTANT ] { 1 }
Constant [ protected integer PROTECTED_CONSTANT ] { 0 }
Constant [ private string PRIVATE_CONSTANT ] { string }
Constant [ public array NO_VISIBILITY_CONSTANT ] { Array }
+ /**
+ * @var string
+ */
+ Constant [ public string WITH_DOC_COMMENT_CONSTANT ] { string }
}
- Static properties [1] {
@@ -14,11 +18,11 @@ Class [ abstract class Roave\BetterReflectionTest\Fixture\StringCastClass
- Static methods [1] {
Method [ static public method staticPublicMethod ] {
- @@ %s/Fixture/StringCastClass.php 70 - 72
+ @@ %s/Fixture/StringCastClass.php 80 - 82
}
}
- - Properties [7] {
+ - Properties [8] {
Property [ private $privateProperty ]
Property [ protected $protectedProperty ]
Property [ public $publicProperty ]
@@ -26,51 +30,55 @@ Class [ abstract class Roave\BetterReflectionTest\Fixture\StringCastClass
Property [ public int|bool $unionTypeProperty ]
Property [ public ?int $nullableTypeProperty ]
Property [ public readonly int $readOnlyProperty ]
+ /**
+ * @var string
+ */
+ Property [ public $propertyWithDocComment ]
}
- Methods [12] {
Method [ public method __construct ] {
- @@ %s/Fixture/StringCastClass.php 44 - 46
+ @@ %s/Fixture/StringCastClass.php 54 - 56
}
Method [ public method __destruct ] {
- @@ %s/Fixture/StringCastClass.php 48 - 50
+ @@ %s/Fixture/StringCastClass.php 58 - 60
}
Method [ public method publicMethod ] {
- @@ %s/Fixture/StringCastClass.php 52 - 54
+ @@ %s/Fixture/StringCastClass.php 62 - 64
}
Method [ protected method protectedMethod ] {
- @@ %s/Fixture/StringCastClass.php 56 - 58
+ @@ %s/Fixture/StringCastClass.php 66 - 68
}
Method [ private method privateMethod ] {
- @@ %s/Fixture/StringCastClass.php 60 - 62
+ @@ %s/Fixture/StringCastClass.php 70 - 72
}
Method [ final public method finalPublicMethod ] {
- @@ %s/Fixture/StringCastClass.php 64 - 66
+ @@ %s/Fixture/StringCastClass.php 74 - 76
}
Method [ abstract public method abstractPublicMethod ] {
- @@ %s/Fixture/StringCastClass.php 68 - 68
+ @@ %s/Fixture/StringCastClass.php 78 - 78
}
Method [ public method noVisibility ] {
- @@ %s/Fixture/StringCastClass.php 74 - 76
+ @@ %s/Fixture/StringCastClass.php 84 - 86
}
Method [ public method overwrittenMethod ] {
- @@ %s/Fixture/StringCastClass.php 78 - 80
+ @@ %s/Fixture/StringCastClass.php 88 - 90
}
Method [ public method prototypeMethod ] {
- @@ %s/Fixture/StringCastClass.php 82 - 84
+ @@ %s/Fixture/StringCastClass.php 92 - 94
}
Method [ public method methodWithParameters ] {
- @@ %s/Fixture/StringCastClass.php 86 - 88
+ @@ %s/Fixture/StringCastClass.php 96 - 98
- Parameters [2] {
Parameter #0 [ $a ]
diff --git a/test/unit/Fixture/StringCastProperties.php b/test/unit/Fixture/StringCastProperties.php
index cfc1fff34..579e6c384 100644
--- a/test/unit/Fixture/StringCastProperties.php
+++ b/test/unit/Fixture/StringCastProperties.php
@@ -14,4 +14,9 @@ class StringCastProperties
public ?int $nullableTypeProperty = null;
public readonly int $readOnlyProperty;
+
+ /**
+ * @var string
+ */
+ public $propertyWithDocComment;
}
diff --git a/test/unit/Fixture/StringCastPureEnum.php b/test/unit/Fixture/StringCastPureEnum.php
index 3d0bd6206..b8131c0f8 100644
--- a/test/unit/Fixture/StringCastPureEnum.php
+++ b/test/unit/Fixture/StringCastPureEnum.php
@@ -7,4 +7,9 @@ enum StringCastPureEnum
case ENUM_CASE;
const CONSTANT = 'constant';
+
+ /**
+ * Something
+ */
+ case ENUM_CASE_WITH_DOC_COMMENT;
}
diff --git a/test/unit/Fixture/StringCastPureEnumExpected.txt b/test/unit/Fixture/StringCastPureEnumExpected.txt
index 3de875c54..293829348 100644
--- a/test/unit/Fixture/StringCastPureEnumExpected.txt
+++ b/test/unit/Fixture/StringCastPureEnumExpected.txt
@@ -1,8 +1,12 @@
Class [ final class Roave\BetterReflectionTest\Fixture\StringCastPureEnum implements UnitEnum ] {
- @@ %s/Fixture/StringCastPureEnum.php 5-10
+ @@ %s/Fixture/StringCastPureEnum.php 5-15
- - Constants [2] {
+ - Constants [3] {
Constant [ public Roave\BetterReflectionTest\Fixture\StringCastPureEnum ENUM_CASE ] { Object }
+ /**
+ * Something
+ */
+ Constant [ public Roave\BetterReflectionTest\Fixture\StringCastPureEnum ENUM_CASE_WITH_DOC_COMMENT ] { Object }
Constant [ public string CONSTANT ] { constant }
}
diff --git a/test/unit/Reflection/Adapter/ReflectionAttributeTest.php b/test/unit/Reflection/Adapter/ReflectionAttributeTest.php
index ea2e537e7..9d536ea37 100644
--- a/test/unit/Reflection/Adapter/ReflectionAttributeTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionAttributeTest.php
@@ -4,12 +4,13 @@
namespace Roave\BetterReflectionTest\Reflection\Adapter;
+use OutOfBoundsException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ReflectionAttribute as CoreReflectionAttribute;
use ReflectionClass as CoreReflectionClass;
-use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
@@ -46,7 +47,7 @@ public static function methodExpectationProvider(): array
['getTarget', null, 1, []],
['isRepeated', null, false, []],
['getArguments', null, [], []],
- ['newInstance', NotImplemented::class, null, []],
+ ['newInstance', NotImplementedBecauseItTriggersAutoloading::class, null, []],
];
}
@@ -70,4 +71,26 @@ public function testAdapterMethods(string $methodName, string|null $expectedExce
$adapter = new ReflectionAttributeAdapter($reflectionStub);
$adapter->{$methodName}(...$args);
}
+
+ public function testPropertyName(): void
+ {
+ $betterReflectionAttribute = $this->createMock(BetterReflectionAttribute::class);
+ $betterReflectionAttribute
+ ->method('getName')
+ ->willReturn('Foo');
+
+ $reflectionAttributeAdapter = new ReflectionAttributeAdapter($betterReflectionAttribute);
+ self::assertSame('Foo', $reflectionAttributeAdapter->name);
+ }
+
+ public function testUnknownProperty(): void
+ {
+ $this->expectException(OutOfBoundsException::class);
+ $this->expectExceptionMessage('Property Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute::$foo does not exist.');
+
+ $betterReflectionAttribute = $this->createMock(BetterReflectionAttribute::class);
+ $reflectionAttributeAdapter = new ReflectionAttributeAdapter($betterReflectionAttribute);
+ /** @phpstan-ignore property.notFound, expr.resultUnused */
+ $reflectionAttributeAdapter->foo;
+ }
}
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..eb87ae18a 100644
--- a/test/unit/Reflection/Adapter/ReflectionClassTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionClassTest.php
@@ -15,6 +15,7 @@
use ReflectionMethod as CoreReflectionMethod;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
@@ -91,9 +92,9 @@ public static function methodExpectationProvider(): array
['isReadOnly', [], true, null, true],
['getModifiers', [], 123, null, 123],
['isInstance', [new stdClass()], true, null, true],
- ['newInstance', [], null, NotImplemented::class, null],
- ['newInstanceWithoutConstructor', [], null, NotImplemented::class, null],
- ['newInstanceArgs', [], null, NotImplemented::class, null],
+ ['newInstance', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceWithoutConstructor', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceArgs', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
['getParentClass', [], null, null, null],
['isSubclassOf', ['\stdClass'], true, null, true],
['getStaticProperties', [], [], null, []],
@@ -1286,4 +1287,92 @@ public function testGetTraits(): void
self::assertArrayHasKey($traitOneClassName, $traits);
self::assertArrayHasKey($traitTwoClassName, $traits);
}
+
+ public function testNewLazyGhost(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
+
+ $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
+ $reflectionClassAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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..283723352 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumBackedCaseTest.php
@@ -10,7 +10,7 @@
use PHPUnit\Framework\TestCase;
use ReflectionClass as CoreReflectionClass;
use ReflectionEnumBackedCase as CoreReflectionEnumBackedCase;
-use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionEnum as ReflectionEnumAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase as ReflectionEnumBackedCaseAdapter;
@@ -51,9 +51,10 @@ public static function methodExpectationProvider(): array
// Inherited
['__toString', null, '', []],
['getName', null, '', []],
- ['getValue', NotImplemented::class, null, []],
+ ['getValue', NotImplementedBecauseItTriggersAutoloading::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..e6ccc2201 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumTest.php
@@ -16,6 +16,7 @@
use ReflectionMethod as CoreReflectionMethod;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
@@ -97,9 +98,9 @@ public static function methodExpectationProvider(): array
['isReadOnly', [], true, null, true],
['getModifiers', [], 123, null, 123],
['isInstance', [new stdClass()], true, null, true],
- ['newInstance', [], null, NotImplemented::class, null],
- ['newInstanceWithoutConstructor', [], null, NotImplemented::class, null],
- ['newInstanceArgs', [], null, NotImplemented::class, null],
+ ['newInstance', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceWithoutConstructor', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceArgs', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
['isSubclassOf', ['\stdClass'], true, null, true],
['getStaticProperties', [], [], null, []],
['getDefaultProperties', [], ['foo' => 'bar'], null, null],
@@ -1140,4 +1141,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(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionEnum = $this->createMock(BetterReflectionEnum::class);
+
+ $reflectionEnumAdapter = new ReflectionEnumAdapter($betterReflectionEnum);
+ $reflectionEnumAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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..9d5dfe30a 100644
--- a/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionEnumUnitCaseTest.php
@@ -10,7 +10,7 @@
use PHPUnit\Framework\TestCase;
use ReflectionClass as CoreReflectionClass;
use ReflectionEnumUnitCase as CoreReflectionEnumUnitCase;
-use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionEnum as ReflectionEnumAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase as ReflectionEnumUnitCaseAdapter;
@@ -51,9 +51,10 @@ public static function methodExpectationProvider(): array
// Inherited
['__toString', null, '', []],
['getName', null, '', []],
- ['getValue', NotImplemented::class, null, []],
+ ['getValue', NotImplementedBecauseItTriggersAutoloading::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..60f1ec51c 100644
--- a/test/unit/Reflection/Adapter/ReflectionObjectTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionObjectTest.php
@@ -15,6 +15,7 @@
use ReflectionObject as CoreReflectionObject;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
+use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplementedBecauseItTriggersAutoloading;
use Roave\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
@@ -90,9 +91,9 @@ public static function methodExpectationProvider(): array
['isReadOnly', [], true, null, true],
['getModifiers', [], 123, null, 123],
['isInstance', [new stdClass()], true, null, true],
- ['newInstance', [], null, NotImplemented::class, null],
- ['newInstanceWithoutConstructor', [], null, NotImplemented::class, null],
- ['newInstanceArgs', [], null, NotImplemented::class, null],
+ ['newInstance', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceWithoutConstructor', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
+ ['newInstanceArgs', [], null, NotImplementedBecauseItTriggersAutoloading::class, null],
['getParentClass', [], null, null, null],
['isSubclassOf', ['\stdClass'], true, null, true],
['getStaticProperties', [], [], null, []],
@@ -1239,4 +1240,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(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->newLazyGhost(static fn () => null);
+ }
+
+ public function testNewLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->newLazyProxy(static fn () => null);
+ }
+
+ public function testMarkLazyObjectAsInitialized(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->markLazyObjectAsInitialized(new stdClass());
+ }
+
+ public function testGetLazyInitializer(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->getLazyInitializer(new stdClass());
+ }
+
+ public function testInitializeLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->initializeLazyObject(new stdClass());
+ }
+
+ public function testIsUninitializedLazyObject(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->isUninitializedLazyObject(new stdClass());
+ }
+
+ public function testResetAsLazyGhost(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionObject = $this->createMock(BetterReflectionObject::class);
+
+ $reflectionObjectAdapter = new ReflectionObjectAdapter($betterReflectionObject);
+ $reflectionObjectAdapter->resetAsLazyGhost(new stdClass(), static fn () => null);
+ }
+
+ public function testResetAsLazyProxy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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..6ad0dcc19 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\NotImplementedBecauseItTriggersAutoloading;
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(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->setRawValueWithoutLazyInitialization(new stdClass(), null);
+ }
+
+ public function testIsLazy(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $betterReflectionProperty = $this->createMock(BetterReflectionProperty::class);
+ $reflectionPropertyAdapter = new ReflectionPropertyAdapter($betterReflectionProperty);
+ $reflectionPropertyAdapter->isLazy(new stdClass());
+ }
+
+ public function testSkipLazyInitialization(): void
+ {
+ self::expectException(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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(NotImplementedBecauseItTriggersAutoloading::class);
+ self::expectExceptionMessage('Not implemented because it triggers autoloading');
+
+ $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..0bebcd39b
--- /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->reflectFunction('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..32f6e6884 100644
--- a/test/unit/Reflection/ReflectionClassConstantTest.php
+++ b/test/unit/Reflection/ReflectionClassConstantTest.php
@@ -100,7 +100,7 @@ public function testProtectedFinal(): void
public function testToString(): void
{
- self::assertSame("Constant [ public integer MY_CONST_1 ] { 123 }\n", (string) $this->getExampleConstant('MY_CONST_1'));
+ self::assertSame("/**\n * This comment for constant should be used.\n */\nConstant [ public integer MY_CONST_2 ] { 234 }\n", (string) $this->getExampleConstant('MY_CONST_2'));
}
/** @param non-empty-string $const */
@@ -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..d87f95982 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, 3],
+ [ReflectionPropertyAdapter::IS_VIRTUAL_COMPATIBILITY, 1],
+ ];
+ }
+
+ /** @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,7 +2859,7 @@ public function __toString(): string
}
/** @return list */
- public static function deprecatedDocCommentProvider(): array
+ public static function deprecatedProvider(): array
{
return [
[
@@ -2815,12 +2881,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/ReflectionEnumCaseTest.php b/test/unit/Reflection/ReflectionEnumCaseTest.php
index a1ddad5d9..781db1259 100644
--- a/test/unit/Reflection/ReflectionEnumCaseTest.php
+++ b/test/unit/Reflection/ReflectionEnumCaseTest.php
@@ -114,9 +114,9 @@ public function testGetValueThrowsExceptionForPureEnum(): void
public static function dataLinesAndColumns(): array
{
return [
- [PureEnum::class, 'ONE', 7, 7, 5, 13],
- [IntEnum::class, 'TWO', 19, 19, 5, 17],
- [StringEnum::class, 'THREE', 34, 35, 5, 18],
+ [PureEnum::class, 'ONE', 11, 11, 5, 13],
+ [IntEnum::class, 'TWO', 23, 23, 5, 17],
+ [StringEnum::class, 'THREE', 38, 39, 5, 18],
];
}
@@ -251,6 +251,6 @@ public function testToString(): void
self::assertInstanceOf(ReflectionEnum::class, $enumReflection);
- self::assertSame("Constant [ public Roave\BetterReflectionTest\Fixture\PureEnum ONE ] { Object }\n", (string) $enumReflection->getCase('ONE'));
+ self::assertSame("/**\n * One\n */\nConstant [ public Roave\BetterReflectionTest\Fixture\PureEnum ONE ] { Object }\n", (string) $enumReflection->getCase('ONE'));
}
}
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,12 +316,13 @@ public function testIsDefaultWithRuntimeDeclaredProperty(): void
);
self::assertFalse($propertyNode->isDefault());
+ self::assertTrue($propertyNode->isDynamic());
}
public function testToString(): void
{
$classInfo = $this->reflector->reflectClass(ExampleClass::class);
- self::assertSame('Property [ public $publicProperty ]', (string) $classInfo->getProperty('publicProperty'));
+ self::assertSame("/**\n * @var string\n */\nProperty [ public \$publicProperty ]", (string) $classInfo->getProperty('publicProperty'));
}
/** @return list */
@@ -861,4 +893,232 @@ 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->isAbstract());
+ 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 testHooksInInterface(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\InterfacePropertyHooks');
+
+ $readOnlyHookProperty = $classInfo->getProperty('readOnlyHook');
+
+ self::assertTrue($readOnlyHookProperty->isDefault());
+ self::assertTrue($readOnlyHookProperty->isVirtual());
+ self::assertTrue($readOnlyHookProperty->hasHooks());
+ self::assertCount(1, $readOnlyHookProperty->getHooks());
+
+ $writeOnlyHookProperty = $classInfo->getProperty('writeOnlyHook');
+
+ self::assertTrue($writeOnlyHookProperty->isDefault());
+ self::assertTrue($writeOnlyHookProperty->isVirtual());
+ self::assertTrue($writeOnlyHookProperty->hasHooks());
+ self::assertCount(1, $writeOnlyHookProperty->getHooks());
+
+ $readAndWriteHookProperty = $classInfo->getProperty('readAndWriteHook');
+
+ self::assertTrue($readAndWriteHookProperty->isDefault());
+ self::assertTrue($readAndWriteHookProperty->isVirtual());
+ self::assertTrue($readAndWriteHookProperty->hasHooks());
+ self::assertCount(2, $readAndWriteHookProperty->getHooks());
+ }
+
+ /** @return list */
+ public static function virtualProvider(): array
+ {
+ return [
+ ['notVirtualBecauseNoHooks', false],
+ ['notVirtualBecauseOfPublicVisibilityAndThePropertyIsUsedInGet', false],
+ ['virtualBecauseOfNotPublicVisibilityAndNoSet', true],
+ ['notVirtualBecauseOfShortSyntax', false],
+ ['virtualBecauseThePropertyIsNotUsedInGet', true],
+ ['virtualBecauseSetWorksWithDifferentProperty', true],
+ ['notVirtualBecauseIsPublicSoTheSetWithDifferentPropertyIsNotRelevant', false],
+ ['virtualBecauseGetAndSetAbstract', true],
+ ['notVirtualBecauseSetIsNotAbstract', false],
+ ];
+ }
+
+ #[DataProvider('virtualProvider')]
+ public function testVirtual(string $propertyName, bool $isVirtual): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\ToBeVirtualOrNotToBeVirtualThatIsTheQuestion');
+
+ $hookProperty = $classInfo->getProperty($propertyName);
+ self::assertSame($isVirtual, $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::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::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::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/Reflection/StringCast/ReflectionClassConstantStringCastTest.php b/test/unit/Reflection/StringCast/ReflectionClassConstantStringCastTest.php
index b1ea583f1..03efe6c02 100644
--- a/test/unit/Reflection/StringCast/ReflectionClassConstantStringCastTest.php
+++ b/test/unit/Reflection/StringCast/ReflectionClassConstantStringCastTest.php
@@ -35,6 +35,7 @@ public static function toStringProvider(): array
['PRIVATE_CONSTANT', "Constant [ private string PRIVATE_CONSTANT ] { string }\n"],
['NO_VISIBILITY_CONSTANT', "Constant [ public array NO_VISIBILITY_CONSTANT ] { Array }\n"],
['FINAL_CONSTANT', "Constant [ final public string FINAL_CONSTANT ] { final }\n"],
+ ['WITH_DOC_COMMENT_CONSTANT', "/**\n * @var string\n */\nConstant [ public string WITH_DOC_COMMENT_CONSTANT ] { string }\n"],
];
}
diff --git a/test/unit/Reflection/StringCast/ReflectionEnumCaseStringCastTest.php b/test/unit/Reflection/StringCast/ReflectionEnumCaseStringCastTest.php
index 57782eb8b..88cbfad46 100644
--- a/test/unit/Reflection/StringCast/ReflectionEnumCaseStringCastTest.php
+++ b/test/unit/Reflection/StringCast/ReflectionEnumCaseStringCastTest.php
@@ -36,6 +36,15 @@ public function testPureEnumCaseToString(): void
self::assertSame("Constant [ public Roave\BetterReflectionTest\Fixture\StringCastPureEnum ENUM_CASE ] { Object }\n", (string) $enumReflection->getCase('ENUM_CASE'));
}
+ public function testPureEnumCaseWithDocCommentToString(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../../Fixture/StringCastPureEnum.php', $this->astLocator));
+ $enumReflection = $reflector->reflectClass(StringCastPureEnum::class);
+
+ self::assertInstanceOf(ReflectionEnum::class, $enumReflection);
+ self::assertSame("/**\n * Something\n */\nConstant [ public Roave\BetterReflectionTest\Fixture\StringCastPureEnum ENUM_CASE_WITH_DOC_COMMENT ] { Object }\n", (string) $enumReflection->getCase('ENUM_CASE_WITH_DOC_COMMENT'));
+ }
+
public function testBackedEnumCaseToString(): void
{
$reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../../Fixture/StringCastBackedEnum.php', $this->astLocator));
@@ -44,4 +53,13 @@ public function testBackedEnumCaseToString(): void
self::assertInstanceOf(ReflectionEnum::class, $enumReflection);
self::assertSame("Constant [ public string ENUM_CASE ] { string }\n", (string) $enumReflection->getCase('ENUM_CASE'));
}
+
+ public function testBackedEnumCaseWithDocCommentToString(): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../../Fixture/StringCastBackedEnum.php', $this->astLocator));
+ $enumReflection = $reflector->reflectClass(StringCastBackedEnum::class);
+
+ self::assertInstanceOf(ReflectionEnum::class, $enumReflection);
+ self::assertSame("/**\n * Something\n */\nConstant [ public string ENUM_CASE_WITH_DOC_COMMENT ] { something }\n", (string) $enumReflection->getCase('ENUM_CASE_WITH_DOC_COMMENT'));
+ }
}
diff --git a/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php b/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php
index d2307eb3f..0b9372283 100644
--- a/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php
+++ b/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php
@@ -38,6 +38,7 @@ public static function toStringProvider(): array
['unionTypeProperty', 'Property [ public int|bool $unionTypeProperty ]'],
['nullableTypeProperty', 'Property [ public ?int $nullableTypeProperty ]'],
['readOnlyProperty', 'Property [ public readonly int $readOnlyProperty ]'],
+ ['propertyWithDocComment', "/**\n * @var string\n */\nProperty [ public \$propertyWithDocComment ]"],
];
}
diff --git a/test/unit/Reflection/StringCast/ReflectionStringCastHelperTest.php b/test/unit/Reflection/StringCast/ReflectionStringCastHelperTest.php
new file mode 100644
index 000000000..d7a1e3638
--- /dev/null
+++ b/test/unit/Reflection/StringCast/ReflectionStringCastHelperTest.php
@@ -0,0 +1,87 @@
+astLocator = BetterReflectionSingleton::instance()->astLocator();
+ }
+
+ public function testNoDocComment(): void
+ {
+ $phpCode = <<<'PHP'
+ astLocator));
+ $classReflection = $reflector->reflectClass('Foo');
+ $classConstantReflection = $classReflection->getConstant('SOME_CONSTANT');
+
+ self::assertSame('', ReflectionStringCastHelper::docCommentToString($classConstantReflection, false));
+ self::assertSame('', ReflectionStringCastHelper::docCommentToString($classConstantReflection, true));
+ }
+
+ public function testNoDocCommentWithoutIdent(): void
+ {
+ $phpCode = <<<'PHP'
+ astLocator));
+ $classReflection = $reflector->reflectClass('Foo');
+ $classConstantReflection = $classReflection->getConstant('SOME_CONSTANT');
+
+ self::assertSame("/**\n * @var int\n */\n", ReflectionStringCastHelper::docCommentToString($classConstantReflection, false));
+ }
+
+ public function testNoDocCommentWitIdent(): void
+ {
+ $phpCode = <<<'PHP'
+ astLocator));
+ $classReflection = $reflector->reflectClass('Foo');
+ $classConstantReflection = $classReflection->getConstant('SOME_CONSTANT');
+
+ self::assertSame("/**\n * @var int\n */\n", ReflectionStringCastHelper::docCommentToString($classConstantReflection, true));
+ }
+}
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();
},
),
);