From b35d32a4099eb6decd863b9bfc4c94f2dced003e Mon Sep 17 00:00:00 2001 From: Ivan Ponomarev Date: Fri, 18 Aug 2023 18:07:07 +0200 Subject: [PATCH 1/3] #161 / Improved meta-magic --- src/attributes/spells/Serialize.php | 16 ++++++++++++++ src/generic/Spell.php | 34 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/attributes/spells/Serialize.php create mode 100644 src/generic/Spell.php diff --git a/src/attributes/spells/Serialize.php b/src/attributes/spells/Serialize.php new file mode 100644 index 0000000..0fe1ba6 --- /dev/null +++ b/src/attributes/spells/Serialize.php @@ -0,0 +1,16 @@ + Date: Sun, 20 Aug 2023 11:23:20 +0200 Subject: [PATCH 2/3] #161 / Improved meta-magic unfinished * Good progress with finding all the relevant Spells * Added a static method `Boolean::isBitFlagOn()` --- src/Attrs.php | 67 +++++++++++++++++++++++++++++- src/Boolean.php | 15 +++++++ src/attributes/Renderer.php | 12 +++++- src/generic/Spell.php | 4 +- src/traits/StaticRendererTrait.php | 19 ++++++++- 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/Attrs.php b/src/Attrs.php index 827b67e..ca8368b 100644 --- a/src/Attrs.php +++ b/src/Attrs.php @@ -2,11 +2,15 @@ namespace spaf\simputils; +use Attribute; use Closure; use Exception; +use ReflectionAttribute; use ReflectionClass; +use ReflectionClassConstant; use ReflectionMethod; use ReflectionObject; +use Reflector; use spaf\simputils\models\Box; use TypeError; use function class_exists; @@ -47,7 +51,7 @@ static function collectMethodReflections(mixed $instance, string $attr): Box|arr $reflection = new ReflectionClass($instance); } else { throw new TypeError( - '$instance is not Object or Class String or Class does not exist' + '$instance is not Object or Class String or Class does not exist', ); } if (!is_string($attr) || !class_exists($attr)) { @@ -65,4 +69,65 @@ static function collectMethodReflections(mixed $instance, string $attr): Box|arr return $res; } + static function findSpells( + string|object $instance, + null|Box|array $attrs = null, + $attr_target = Attribute::TARGET_ALL, + ?callable $callback = null, + ) { + + $attr_target = Attribute::TARGET_ALL | $attr_target; + + $r = new ReflectionClass($instance); + $res = PHP::box(); + + $attrs = PHP::box($attrs); + + if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_CLASS)) { + static::_processAttributes($res, $r, $t, $attrs, $callback); + } + + if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_PROPERTY)) { + foreach ($r->getProperties() as $ref_item) { + static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + } + } + + if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_METHOD)) { + foreach ($r->getMethods() as $ref_item) { + static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + } + } + + if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_CLASS_CONSTANT)) { + foreach ($r->getConstants() as $key => $val) { + $ref_item = new ReflectionClassConstant($instance, $key); + static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + } + } + + return $res; + } + + static protected function _processAttributes( + &$res, + Reflector $ref_item, + int $target, + Box $attrs, + ?callable $callback, + ) { + foreach ($ref_item->getAttributes() as $at) { + /** @var ReflectionAttribute $at */ + if (!$attrs->size || $attrs->containsValue($at->getName())) { + if (!$callback || $callback($ref_item, $at)) { + $res->append([ + 'target' => $target, + 'reflection' => $ref_item, + 'attribute' => $at->newInstance(), + ]); + } + } + } + } + } diff --git a/src/Boolean.php b/src/Boolean.php index b3ce493..fe3c4b0 100644 --- a/src/Boolean.php +++ b/src/Boolean.php @@ -53,4 +53,19 @@ static function from(mixed $val, bool $strict = false): ?bool { static function to(mixed $val): mixed { return static::from($val)?static::$to_yes:static::$to_no; } + + /** + * Checks whether a bit-flag is on + * + * Basically a shortcut for bit operation "AND" mask + * + * @param int $value + * @param int $flags + * + * @return bool + */ + static function isBitFlagOn(int $value, int $flags): bool { + $res = $value & $flags; + return $res; + } } diff --git a/src/attributes/Renderer.php b/src/attributes/Renderer.php index 06df7c1..4c8c017 100644 --- a/src/attributes/Renderer.php +++ b/src/attributes/Renderer.php @@ -4,6 +4,7 @@ use Attribute; use spaf\simputils\components\RenderedWrapper; +use spaf\simputils\generic\Spell; use spaf\simputils\Html; use spaf\simputils\traits\BaseHtmlTrait; use spaf\simputils\traits\StaticRendererTrait; @@ -17,12 +18,19 @@ * and methods must return either `null` or {@see RenderedWrapper}. * * @see RenderedWrapper Stringifiable object for - * {@see \spaf\simputils\traits\StaticRendererTrait::render()} + * {@see StaticRendererTrait::render()} * @see StaticRendererTrait Trait containing `render` method/functionality. * @see Html Really minimal HTML static class to create/render tags. * @see BaseHtmlTrait Trait containing minimal HTML create/render tag(s) */ #[Attribute(Attribute::TARGET_METHOD)] -class Renderer { +class Renderer extends Spell { + static function getName(): string { + return 'renderer'; + } + + static function invoke(callable $target, ...$spell): mixed { + // TODO: Implement invoke() method. + } } diff --git a/src/generic/Spell.php b/src/generic/Spell.php index 6200549..2b76b5b 100644 --- a/src/generic/Spell.php +++ b/src/generic/Spell.php @@ -27,8 +27,6 @@ abstract static function getName(): string; * * @return mixed */ - static function invoke(callable $target, ...$spell): mixed { - return $target(...$spell); - } + abstract static function invoke(callable $target, ...$spell): mixed; } diff --git a/src/traits/StaticRendererTrait.php b/src/traits/StaticRendererTrait.php index f75afb4..2b1c0c8 100644 --- a/src/traits/StaticRendererTrait.php +++ b/src/traits/StaticRendererTrait.php @@ -3,6 +3,7 @@ namespace spaf\simputils\traits; use ReflectionMethod; +use spaf\simputils\attributes\markers\Shortcut; use spaf\simputils\attributes\Renderer; use spaf\simputils\Attrs; use spaf\simputils\components\RenderedWrapper; @@ -44,8 +45,7 @@ static function render(mixed ...$params): string { try { $res = $method_reflection->invoke($instance, ...$params); - } - catch (TypeError) { + } catch (TypeError) { $res = null; } @@ -69,6 +69,21 @@ static function render(mixed ...$params): string { return $res; } + /** + * Shortcut for render method + * + * @param mixed ...$params + * + * @return string + * @throws \Exception + * @see Renderer + * @see RenderedWrapper + */ + #[Shortcut('static::render()')] + static function r(mixed ...$params): string { + return static::render(...$params); + } + #[Renderer] static private function defaultRenderer($arg = null): RenderedWrapper { return new RenderedWrapper($arg); From 01eaf27d0d5b982b2bba5835e05c08320be54abe Mon Sep 17 00:00:00 2001 From: Ivan Ponomarev Date: Mon, 21 Aug 2023 00:47:01 +0200 Subject: [PATCH 3/3] #161 / Improved meta-magic unfinished * Improved filtering of `\spaf\simputils\Attrs::findSpells()`. Now properties might be compatible with this functionality --- src/Attrs.php | 87 +++++++++++++++++++++++++++------- src/attributes/Property.php | 4 ++ src/traits/PropertiesTrait.php | 12 ++--- 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/Attrs.php b/src/Attrs.php index ca8368b..aa3c4cb 100644 --- a/src/Attrs.php +++ b/src/Attrs.php @@ -8,6 +8,7 @@ use ReflectionAttribute; use ReflectionClass; use ReflectionClassConstant; +use ReflectionException; use ReflectionMethod; use ReflectionObject; use Reflector; @@ -69,40 +70,68 @@ static function collectMethodReflections(mixed $instance, string $attr): Box|arr return $res; } + /** + * @param string|object $instance + * @param null|Box|array $attrs + * @param int $target + * @param null|callable $callback Must return true, false or null. If null returned, then + * no further attributes are checked. + * params ( + * $attr_instance + * ReflectionAttribute $attr, + * Reflector $item, + * string|object $instance, + * $all_attrs + * ) + * @param bool $find_first Find first matching Spell + * + * @return array|Box + * @throws ReflectionException + */ static function findSpells( - string|object $instance, + string|object $instance, null|Box|array $attrs = null, - $attr_target = Attribute::TARGET_ALL, - ?callable $callback = null, + int $target = Attribute::TARGET_ALL, + ?callable $callback = null, + bool $find_first = false, ) { - $attr_target = Attribute::TARGET_ALL | $attr_target; + $target = Attribute::TARGET_ALL | $target; $r = new ReflectionClass($instance); $res = PHP::box(); $attrs = PHP::box($attrs); - if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_CLASS)) { - static::_processAttributes($res, $r, $t, $attrs, $callback); + if (Boolean::isBitFlagOn($target, $t = Attribute::TARGET_CLASS)) { + static::_processAttributes($res, $r, $t, $attrs, $callback, $instance, $find_first); + if ($find_first && $res->size > 0) { + return $res; + } } - if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_PROPERTY)) { + if (Boolean::isBitFlagOn($target, $t = Attribute::TARGET_PROPERTY)) { foreach ($r->getProperties() as $ref_item) { - static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + static::_processAttributes($res, $ref_item, $t, $attrs, $callback, $instance, $find_first); + if ($find_first && $res->size > 0) { + return $res; + } } } - if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_METHOD)) { + if (Boolean::isBitFlagOn($target, $t = Attribute::TARGET_METHOD)) { foreach ($r->getMethods() as $ref_item) { - static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + static::_processAttributes($res, $ref_item, $t, $attrs, $callback, $instance, $find_first); + if ($find_first && $res->size > 0) { + return $res; + } } } - if (Boolean::isBitFlagOn($attr_target, $t = Attribute::TARGET_CLASS_CONSTANT)) { + if (Boolean::isBitFlagOn($target, $t = Attribute::TARGET_CLASS_CONSTANT)) { foreach ($r->getConstants() as $key => $val) { $ref_item = new ReflectionClassConstant($instance, $key); - static::_processAttributes($res, $ref_item, $t, $attrs, $callback); + static::_processAttributes($res, $ref_item, $t, $attrs, $callback, $instance, $find_first); } } @@ -115,16 +144,42 @@ static protected function _processAttributes( int $target, Box $attrs, ?callable $callback, + $instance, + $find_first, ) { - foreach ($ref_item->getAttributes() as $at) { + foreach ($sub_attrs = $ref_item->getAttributes() as $at) { /** @var ReflectionAttribute $at */ if (!$attrs->size || $attrs->containsValue($at->getName())) { - if (!$callback || $callback($ref_item, $at)) { + if (!$callback) { + $res->append([ + 'instance' => $instance, + 'target' => $target, + 'item_reflection' => $ref_item, + 'attr' => $at->newInstance(), + 'attr_reflection' => $at, + ]); + if ($find_first) { + return; + } + } else { + $at_instance = $at->newInstance(); + $cbk_res = $callback($at_instance, $at, $ref_item, $instance, $sub_attrs); + if ($cbk_res === false) { + continue; + } $res->append([ + 'instance' => $instance, 'target' => $target, - 'reflection' => $ref_item, - 'attribute' => $at->newInstance(), + 'item_reflection' => $ref_item, + 'attr' => $at_instance, + 'attr_reflection' => $at, ]); + if ($find_first) { + return; + } + if ($cbk_res === null) { + break; + } } } } diff --git a/src/attributes/Property.php b/src/attributes/Property.php index 85c12fa..ac8f0cc 100644 --- a/src/attributes/Property.php +++ b/src/attributes/Property.php @@ -184,4 +184,8 @@ public static function methodAccessType($ref, \ReflectionAttribute $attr, $args return $method_type; } + + function getFinalType() { + + } } diff --git a/src/traits/PropertiesTrait.php b/src/traits/PropertiesTrait.php index 790ee13..bc86803 100644 --- a/src/traits/PropertiesTrait.php +++ b/src/traits/PropertiesTrait.php @@ -64,9 +64,9 @@ trait PropertiesTrait { * @param string $name Name of the property * * @return mixed - * @throws \spaf\simputils\exceptions\PropertyDoesNotExist Property does not exist - * @throws \spaf\simputils\exceptions\PropertyIsReadOnly Property is read-only - * @throws \spaf\simputils\exceptions\PropertyIsWriteOnly Property is write-only + * @throws PropertyDoesNotExist Property does not exist + * @throws PropertyIsReadOnly Property is read-only + * @throws PropertyIsWriteOnly Property is write-only */ public function __get($name) { $ref = static::class.'#'.$name.'#'.Property::TYPE_GET; @@ -185,9 +185,9 @@ private function getAllTheLastMethodsAndProperties() { * relevant only for {@see __isset()}) * * @return bool - * @throws \spaf\simputils\exceptions\PropertyDoesNotExist Property does not exist - * @throws \spaf\simputils\exceptions\PropertyIsReadOnly Property is read-only - * @throws \spaf\simputils\exceptions\PropertyIsWriteOnly Property is write-only + * @throws PropertyDoesNotExist Property does not exist + * @throws PropertyIsReadOnly Property is read-only + * @throws PropertyIsWriteOnly Property is write-only */ private function _simpUtilsPrepareProperty( string $name,