From 017a8b1ffb283da2e9794f544674606936f64787 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Tue, 30 Jan 2024 12:28:50 +0100 Subject: [PATCH] Split up Reflect into object_ and class_ specific functions --- composer.json | 3 ++ docs/reflect.md | 52 ++++++++++++++++++ src/Reflect/class_attributes.php | 35 ++++++++++++ src/Reflect/class_has_attribute.php | 19 +++++++ src/Reflect/class_is_dynamic.php | 23 ++++++++ tests/unit/Reflect/ClassAttributesTest.php | 56 ++++++++++++++++++++ tests/unit/Reflect/ClassHasAttributeTest.php | 31 +++++++++++ tests/unit/Reflect/ClassIsDynamicTest.php | 43 +++++++++++++++ 8 files changed, 262 insertions(+) create mode 100644 src/Reflect/class_attributes.php create mode 100644 src/Reflect/class_has_attribute.php create mode 100644 src/Reflect/class_is_dynamic.php create mode 100644 tests/unit/Reflect/ClassAttributesTest.php create mode 100644 tests/unit/Reflect/ClassHasAttributeTest.php create mode 100644 tests/unit/Reflect/ClassIsDynamicTest.php diff --git a/composer.json b/composer.json index 66f58e7..e8917e6 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,9 @@ "src/Lens/properties.php", "src/Iso/compose.php", "src/Iso/object_data.php", + "src/Reflect/class_attributes.php", + "src/Reflect/class_has_attribute.php", + "src/Reflect/class_is_dynamic.php", "src/Reflect/instantiate.php", "src/Reflect/object_attributes.php", "src/Reflect/object_has_attribute.php", diff --git a/docs/reflect.md b/docs/reflect.md index c25aabf..c0b7707 100644 --- a/docs/reflect.md +++ b/docs/reflect.md @@ -9,6 +9,58 @@ This component provides runtime-safe reflections on objects. This package provides following functions for dealing with objects. +#### class_attributes + +Detects all attributes at the class level of the given className that match the optionally provided argument type (or super-type). +If the class is not reflectable or there is an error instantiating any argument, an `UnreflectableException` exception is triggered! +The result of this function is of type: `list`. However, if you provide an argument name: psalm will know the type of the attribute. + +```php +use function VeeWee\Reflecta\Reflect\class_attributes; + +try { + $allAttributes = class_attributes(YourClass::name); + $allAttributesOfType = class_attributes(YourClass::name, \YourAttributeType::class); + $allAttributesOfType = class_attributes(YourClass::name, \YourAbstractBaseType::class); +} catch (UnreflectableException) { + // Deal with it +} +``` + +#### class_has_attribute + +Checks if the class contains an attribute of given type (or super-type). +If the class is not reflectable, an `UnreflectableException` exception is triggered! + +```php +use function VeeWee\Reflecta\Reflect\object_has_attribute; + +try { + $hasAttribute = class_has_attribute(YourClass::name, \YourAttributeType::class); + $hasAttributeThatImplementsBaseType = class_has_attribute(YourClass::name, \YourAbstractBaseType::class); +} catch (UnreflectableException) { + // Deal with it +} +``` + +#### class_is_dynamic + +Checks if the provided class is considered a safe dynamic object that implements `AllowDynamicProperties`. +Since this property was only added in PHP 8.1, all older versions will always return `true` and allow adding dynamic properties to that class. +If the object is not reflectable, an `UnreflectableException` exception is triggered! + +```php +use function VeeWee\Reflecta\Reflect\class_is_dynamic; + +try { + $isDynamic = class_is_dynamic(new stdClass()); + $isDynamic = class_is_dynamic(new #[\AllowDynamicProperties] class() {}); + $isNotDynamic = class_is_dynamic(new class() {}); +} catch (UnreflectableException) { + // Deal with it +} +``` + #### instantiate This function instantiates a new object of the provided type by bypassing the constructor. diff --git a/src/Reflect/class_attributes.php b/src/Reflect/class_attributes.php new file mode 100644 index 0000000..1ebe0fc --- /dev/null +++ b/src/Reflect/class_attributes.php @@ -0,0 +1,35 @@ +|null $attributeClassName + * @return (T is null ? list : list) + * + * @throws UnreflectableException + */ +function class_attributes(string $className, ?string $attributeClassName = null): array +{ + $propertyInfo = reflect_class($className); + + return map( + $propertyInfo->getAttributes($attributeClassName, ReflectionAttribute::IS_INSTANCEOF), + static fn (ReflectionAttribute $attribute): object => wrap(static fn () => $attribute->newInstance()) + ->catch( + static fn (Throwable $error) => throw UnreflectableException::nonInstantiatable( + $attribute->getName(), + $error + ) + ) + ->getResult() + ); +} diff --git a/src/Reflect/class_has_attribute.php b/src/Reflect/class_has_attribute.php new file mode 100644 index 0000000..53885c2 --- /dev/null +++ b/src/Reflect/class_has_attribute.php @@ -0,0 +1,19 @@ +getAttributes($attributeClassName, ReflectionAttribute::IS_INSTANCEOF); +} diff --git a/src/Reflect/class_is_dynamic.php b/src/Reflect/class_is_dynamic.php new file mode 100644 index 0000000..fe41e48 --- /dev/null +++ b/src/Reflect/class_is_dynamic.php @@ -0,0 +1,23 @@ +expectException(UnreflectableException::class); + $this->expectExceptionMessage('Unable to instantiate class ThisIsAnUnknownAttribute.'); + + class_attributes(get_class($x)); + } +} diff --git a/tests/unit/Reflect/ClassHasAttributeTest.php b/tests/unit/Reflect/ClassHasAttributeTest.php new file mode 100644 index 0000000..4ed3969 --- /dev/null +++ b/tests/unit/Reflect/ClassHasAttributeTest.php @@ -0,0 +1,31 @@ += 80200) { + static::markTestSkipped('On PHP 8.2, all classes are safely dynamic'); + } + + $x = new #[AllowDynamicProperties] class {}; + $y = new class {}; + $s = new stdClass(); + + static::assertTrue(class_is_dynamic(get_class($x))); + static::assertTrue(class_is_dynamic(get_class($y))); + static::assertTrue(class_is_dynamic(get_class($s))); + } +}