diff --git a/src/Attributes/CallableAttr.php b/src/Attributes/CallableAttr.php new file mode 100644 index 0000000..3686044 --- /dev/null +++ b/src/Attributes/CallableAttr.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chevere\Parameter\Attributes; + +use Attribute; +use Chevere\Parameter\Interfaces\ParameterAttributeInterface; +use Chevere\Parameter\Interfaces\ParameterInterface; +use LogicException; +use function Chevere\Parameter\object; + +#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] +class CallableAttr implements ParameterAttributeInterface +{ + public readonly ParameterInterface $parameter; + + public function __construct(callable $callable) + { + $return = $callable(); + object(ParameterInterface::class)($return); + // if (!is_object($return)) { + // throw new LogicException('DynamicAttr must return an object'); + // } + // if (! $return instanceof ParameterInterface) { + // throw new LogicException('DynamicAttr must return an object implementing ParameterInterface'); + // } + $this->parameter = $return; + } + + public function __invoke(mixed $mixed): mixed + { + return $this->parameter->__invoke($mixed); + } + + public function parameter(): ParameterInterface + { + return $this->parameter; + } +} diff --git a/src/Attributes/NullAttr.php b/src/Attributes/NullAttr.php new file mode 100644 index 0000000..d1ff4d8 --- /dev/null +++ b/src/Attributes/NullAttr.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chevere\Parameter\Attributes; + +use Attribute; +use Chevere\Parameter\Interfaces\NullParameterInterface; +use Chevere\Parameter\Interfaces\ParameterAttributeInterface; +use Chevere\Parameter\Interfaces\ParameterInterface; +use function Chevere\Parameter\null; + +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::TARGET_CLASS_CONSTANT)] +class NullAttr implements ParameterAttributeInterface +{ + public readonly NullParameterInterface $parameter; + + public function __construct( + string $description = '', + ) { + $this->parameter = null( + description: $description, + ); + } + + public function __invoke(mixed $null): mixed + { + return $this->parameter->__invoke($null); + } + + public function parameter(): ParameterInterface + { + return $this->parameter; + } +} diff --git a/src/Attributes/functions.php b/src/Attributes/functions.php index 0d6e3ba..792ee73 100644 --- a/src/Attributes/functions.php +++ b/src/Attributes/functions.php @@ -18,6 +18,7 @@ use ReflectionAttribute; use ReflectionFunction; use ReflectionMethod; +use Throwable; use function Chevere\Parameter\parameterAttr; use function Chevere\Parameter\reflectionToParameters; @@ -99,11 +100,14 @@ function arrayArguments(string $name): ArgumentsInterface } /** - * Validates argument `$name` against attribute rules. + * Validates argument `$name` against parameter attribute rules. + * + * @param ?string $name Argument name or `null` to validate all arguments. */ -function validate(?string $name = null): void +function valid(?string $name = null): void { - $caller = debug_backtrace(0, 2)[1]; + $trace = debug_backtrace(0, 2); + $caller = $trace[1]; $class = $caller['class'] ?? false; $method = $caller['function']; $args = $caller['args'] ?? []; @@ -122,7 +126,18 @@ function validate(?string $name = null): void return; } - $parameters->get($name)->__invoke($arguments[$name]); + + try { + $parameters->get($name)->__invoke($arguments[$name]); + } catch (Throwable $e) { + $invoker = $trace[0]; + // @phpstan-ignore-next-line + $fileLine = $invoker['file'] . ':' . $invoker['line']; + + throw new $e( + $e->getMessage() . ' >>> ' . $fileLine + ); + } } /** @@ -130,9 +145,10 @@ function validate(?string $name = null): void * * @return mixed The validated `$var`. */ -function returnAttr(mixed $var): mixed +function validReturn(mixed $var): mixed { - $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $caller = $trace[1]; $class = $caller['class'] ?? null; $method = $caller['function']; $reflection = $class @@ -142,5 +158,15 @@ function returnAttr(mixed $var): mixed $attribute = $reflection->getAttributes(ReturnAttr::class)[0] ?? throw new LogicException('No return attribute found'); - return $attribute->newInstance()->__invoke($var); + try { + return $attribute->newInstance()->__invoke($var); + } catch (Throwable $e) { + $invoker = $trace[0]; + // @phpstan-ignore-next-line + $fileLine = $invoker['file'] . ':' . $invoker['line']; + + throw new $e( + $e->getMessage() . ' >>> ' . $fileLine + ); + } } diff --git a/tests/src/UsesParameterAttributes.php b/tests/src/UsesParameterAttributes.php index 752bbd2..244c890 100644 --- a/tests/src/UsesParameterAttributes.php +++ b/tests/src/UsesParameterAttributes.php @@ -14,20 +14,26 @@ namespace Chevere\Tests\src; use Chevere\Parameter\Attributes\ArrayAttr; +use Chevere\Parameter\Attributes\CallableAttr; use Chevere\Parameter\Attributes\GenericAttr; use Chevere\Parameter\Attributes\IntAttr; use Chevere\Parameter\Attributes\ReturnAttr; use Chevere\Parameter\Attributes\StringAttr; +use Chevere\Parameter\Interfaces\ParameterInterface; use function Chevere\Parameter\Attributes\arrayArguments; use function Chevere\Parameter\Attributes\arrayAttr; use function Chevere\Parameter\Attributes\genericAttr; use function Chevere\Parameter\Attributes\intAttr; -use function Chevere\Parameter\Attributes\returnAttr; use function Chevere\Parameter\Attributes\stringAttr; -use function Chevere\Parameter\Attributes\validate; +use function Chevere\Parameter\Attributes\valid; +use function Chevere\Parameter\Attributes\validReturn; +use function Chevere\Parameter\int; final class UsesParameterAttributes { + #[ReturnAttr( + new CallableAttr(__CLASS__ . '::acceptResponse') + )] public function __construct( #[StringAttr('/^[A-Za-z]+$/')] string $name = 'Test', @@ -42,16 +48,22 @@ public function __construct( )] iterable $tags = [], ) { - validate('name'); - validate('age'); - validate('cols'); - validate('tags'); - validate(); + valid('name'); + valid('age'); + valid('cols'); + valid('tags'); + valid(); $name = stringAttr('name')($name); $age = intAttr('age')($age); $cols = arrayAttr('cols')($cols); $id = arrayArguments('cols')->required('id')->int(); $tags = genericAttr('tags')($tags); + validReturn($id); + } + + public static function acceptResponse(): ParameterInterface + { + return int(); } #[ReturnAttr( @@ -59,6 +71,6 @@ public function __construct( )] public function run(int $int): int { - return returnAttr($int); + return validReturn($int); } } diff --git a/tests/src/functions.php b/tests/src/functions.php index 90861d6..99eaa45 100644 --- a/tests/src/functions.php +++ b/tests/src/functions.php @@ -19,7 +19,7 @@ use Chevere\Parameter\Attributes\IntAttr; use Chevere\Parameter\Attributes\ReturnAttr; use Chevere\Parameter\Attributes\StringAttr; -use function Chevere\Parameter\Attributes\validate; +use function Chevere\Parameter\Attributes\valid; #[ReturnAttr( new BoolAttr() @@ -37,7 +37,7 @@ function myArray( )] array $spooky ): bool { - validate('spooky'); + valid('spooky'); return true; }