| 
<?php
 /*
 * This file is part of Chevere.
 *
 * (c) Rodolfo Berrios <[email protected]>
 *
 * 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 Chevere\Parameter\Exceptions\ParameterException;
 use Chevere\Parameter\Interfaces\ArgumentsInterface;
 use Chevere\Parameter\Interfaces\ParameterInterface;
 use LogicException;
 use ReflectionFunction;
 use ReflectionMethod;
 use Throwable;
 use function Chevere\Message\message;
 use function Chevere\Parameter\parameterAttr;
 use function Chevere\Parameter\reflectionToParameters;
 
 function stringAttr(string $name): StringAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function enumAttr(string $name): EnumAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function intAttr(string $name): IntAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function floatAttr(string $name): FloatAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function boolAttr(string $name): BoolAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function nullAttr(string $name): NullAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function arrayAttr(string $name): ArrayAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function iteratorAttr(string $name): IterableAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function unionAttr(string $name): UnionAttr
 {
 $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
 
 // @phpstan-ignore-next-line
 return parameterAttr($name, $caller['function'], $caller['class'] ?? '');
 }
 
 function returnAttr(): ReturnAttr
 {
 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
 $caller = $trace[1];
 $class = $caller['class'] ?? null;
 $method = $caller['function'];
 $convention = "{$class}::return";
 $reflection = $class
 ? new ReflectionMethod($class, $method)
 : new ReflectionFunction($method);
 $attribute = $reflection->getAttributes(ReturnAttr::class)[0] ?? null;
 if ($attribute === null) {
 if (! is_callable($convention)) {
 throw new LogicException(
 (string) message(
 'No applicable return rules to validate',
 )
 );
 }
 $parameter = $convention();
 if (! $parameter instanceof ParameterInterface) {
 throw new LogicException(
 (string) message(
 'Callable `%callable%` must return a `%type%` instance',
 callable: $convention,
 type: ParameterInterface::class
 )
 );
 }
 } else {
 $attribute = $attribute->newInstance();
 }
 
 /** @var ReturnAttr $attribute */
 return $attribute;
 }
 
 /**
 * Get Arguments for an array parameter.
 */
 function arrayArguments(string $name): ArgumentsInterface
 {
 $caller = debug_backtrace(0, 2)[1];
 $class = $caller['class'] ?? false;
 $method = $caller['function'];
 $args = $caller['args'] ?? [];
 $reflection = $class
 ? new ReflectionMethod($class, $method)
 : new ReflectionFunction($method);
 $parameters = reflectionToParameters($reflection);
 $parameters->assertHas($name);
 $array = match ($parameters->optionalKeys()->contains($name)) {
 true => $parameters->optional($name)->array(),
 default => $parameters->required($name)->array(),
 };
 $pos = -1;
 $arguments = [];
 foreach ($parameters->keys() as $named) {
 $pos++;
 $arguments[$named] = match (true) {
 array_key_exists($pos, $args) => $args[$pos],
 default => $parameters->get($named)->default(),
 };
 }
 
 return $array->parameters()->__invoke(...$arguments[$name]);
 }
 
 /**
 * Validates argument `$name` against parameter attribute rules.
 *
 * @param ?string $name Argument name or `null` to validate all arguments.
 */
 function valid(?string $name = null): void
 {
 $trace = debug_backtrace(0, 2);
 $caller = $trace[1];
 $class = $caller['class'] ?? false;
 $method = $caller['function'];
 $args = $caller['args'] ?? [];
 $reflection = $class
 ? new ReflectionMethod($class, $method)
 : new ReflectionFunction($method);
 $parameters = reflectionToParameters($reflection);
 $pos = -1;
 $arguments = [];
 foreach ($parameters->keys() as $named) {
 $pos++;
 if (! isset($args[$pos])) {
 continue;
 }
 $arguments[$named] = $args[$pos];
 }
 if ($name === null) {
 $parameters(...$arguments);
 
 return;
 }
 if ($parameters->optionalKeys()->contains($name)
 && ! array_key_exists($name, $arguments)
 ) {
 return;
 }
 
 try {
 if (! $parameters->has($name)) {
 throw new LogicException(
 (string) message(
 'Parameter `%name%` not found',
 name: $name,
 )
 );
 }
 $parameter = $parameters->get($name);
 $parameter->__invoke($arguments[$name]);
 } catch (Throwable $e) {
 $invoker = $trace[0];
 $file = $invoker['file'] ?? 'na';
 $line = $invoker['line'] ?? 0;
 
 throw new ParameterException($e->getMessage(), $e, $file, $line);
 }
 }
 
 |