PHP Classes

File: src/functions.php

Recommend this page to a friend!
  Classes of Rodolfo Berrios Arce   Parameter   src/functions.php   Download  
File: src/functions.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Parameter
Validate function parameters with PHP attributes
Author: By
Last change:
Date: 19 days ago
Size: 13,611 bytes
 

Contents

Class file image Download
<?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; use ArrayAccess; use BadMethodCallException; use Chevere\Parameter\Attributes\ReturnAttr; use Chevere\Parameter\Exceptions\AttributeNotFoundException; use Chevere\Parameter\Exceptions\ParameterException; use Chevere\Parameter\Exceptions\ReturnException; use Chevere\Parameter\Interfaces\ArgumentsInterface; use Chevere\Parameter\Interfaces\ArrayParameterInterface; use Chevere\Parameter\Interfaces\CastInterface; use Chevere\Parameter\Interfaces\IterableParameterInterface; use Chevere\Parameter\Interfaces\MixedParameterInterface; use Chevere\Parameter\Interfaces\NullParameterInterface; use Chevere\Parameter\Interfaces\ObjectParameterInterface; use Chevere\Parameter\Interfaces\ParameterAttributeInterface; use Chevere\Parameter\Interfaces\ParameterInterface; use Chevere\Parameter\Interfaces\ParametersAccessInterface; use Chevere\Parameter\Interfaces\ParametersInterface; use Chevere\Parameter\Interfaces\TypeInterface; use Chevere\Parameter\Interfaces\UnionParameterInterface; use InvalidArgumentException; use Iterator; use LogicException; use ReflectionAttribute; use ReflectionFunction; use ReflectionMethod; use ReflectionParameter; use SensitiveParameter; use Throwable; use function Chevere\Message\message; /** * Cast a variable to a CastInterface instance. * * @param mixed $variable The variable to cast. * @param string|int ...$key The key to access in the array (array reduce) */ function cast(mixed $variable, string|int ...$key): CastInterface { if ($key !== []) { if (! ($variable instanceof ArrayAccess || is_array($variable))) { throw new BadMethodCallException( (string) message( 'Argument must be array-accessible, %type% provided', type: gettype($variable) ) ); } $fn = function ($carry, $item) { if (array_key_exists($item, $carry)) { return $carry[$item]; } throw new InvalidArgumentException( (string) message( 'Key `%key%` not found in array', key: $item ) ); }; $variable = array_reduce($key, $fn, $variable); } return new Cast($variable); } function null( string $description = '', ): NullParameterInterface { return new NullParameter($description); } function mixed( string $description = '', bool $sensitive = false, ): MixedParameterInterface { return new MixedParameter($description, $sensitive); } function object( string $className, string $description = '', bool $sensitive = false, ): ObjectParameterInterface { $parameter = new ObjectParameter($description, $sensitive); return $parameter->withClassName($className); } /** * @param ParameterInterface $V Iterable value parameter * @param ParameterInterface|null $K Iterable key parameter */ function iterable( ParameterInterface $V, ?ParameterInterface $K = null, string $description = '', bool $sensitive = false, ): IterableParameterInterface { $K ??= int(); return (new IterableParameter($V, $K, $description))->withIsSensitive($sensitive); } function union( ParameterInterface $one, ParameterInterface $two, ParameterInterface ...$more ): UnionParameterInterface { $parameters = parameters($one, $two, ...$more); return new UnionParameter($parameters); } function parameters( ParameterInterface ...$required, ): ParametersInterface { return new Parameters(...$required); } /** * @phpstan-ignore-next-line */ function arguments( ParametersInterface|ParametersAccessInterface $parameters, array|ArrayAccess $arguments ): ArgumentsInterface { $parameters = getParameters($parameters); return new Arguments($parameters, $arguments); } function assertNamedArgument( string $name, ParameterInterface $parameter, mixed $argument ): ArgumentsInterface { $parameters = parameters( ...[ $name => $parameter, ] ); $arguments = [ $name => $argument, ]; try { return arguments($parameters, $arguments); } catch (Throwable $e) { $message = $e->getMessage(); if (! str_ends_with($name, '*iterable')) { $needle = "[{$name}]: "; $pos = strpos($message, $needle); if ($pos !== false) { $message = substr_replace($message, '', $pos, strlen($needle)); } } throw new InvalidArgumentException( (string) message( 'Argument [%name%]: %message%', name: $name, message: $message, ) ); } } function toUnionParameter(string ...$types): UnionParameterInterface { $parameters = []; foreach ($types as $type) { $parameters[] = toParameter($type); } $parameters = parameters(...$parameters); return new UnionParameter($parameters); } function toParameter(string $type): ParameterInterface { $class = TypeInterface::TYPE_TO_PARAMETER[$type] ?? null; if ($class === null) { $class = TypeInterface::TYPE_TO_PARAMETER['object']; $className = $type; } $arguments = []; if ($class === IterableParameter::class) { $parameter = iterable(mixed()); } else { $parameter = new $class(...$arguments); } if (isset($className)) { // @phpstan-ignore-next-line $parameter = $parameter->withClassName($className); } return $parameter; } function arrayFrom( ParametersAccessInterface|ParametersInterface $parameter, string|int ...$name ): ArrayParameterInterface { return arrayp( ...takeFrom($parameter, ...$name) ); } /** * @return array<string> */ function takeKeys( ParametersAccessInterface|ParametersInterface $parameter, ): array { return getParameters($parameter)->keys(); } /** * @return Iterator<string, ParameterInterface> */ function takeFrom( ParametersAccessInterface|ParametersInterface $parameter, string|int ...$name ): Iterator { $parameters = getParameters($parameter); foreach ($name as $item) { $item = strval($item); yield $item => $parameters->get($item); } } function parametersFrom( ParametersAccessInterface|ParametersInterface $parameter, string ...$name ): ParametersInterface { $parameters = getParameters($parameter); return parameters( ...takeFrom($parameters, ...$name) ); } function getParameters( ParametersAccessInterface|ParametersInterface $parameter ): ParametersInterface { return $parameter instanceof ParametersAccessInterface ? $parameter->parameters() : $parameter; } /** * Retrieves the type of a variable as defined by this library. */ function getType(mixed $variable): string { $type = \gettype($variable); return match ($type) { 'integer' => 'int', 'boolean' => 'bool', 'double' => 'float', 'NULL' => 'null', default => $type, }; } /** * Retrieves a Parameter attribute instance from a function or method parameter. */ function parameterAttr( string $parameter, string $function, string $class = '' ): ParameterAttributeInterface { $reflection = $class !== '' ? new ReflectionMethod($class, $function) : new ReflectionFunction($function); $parameters = $reflection->getParameters(); foreach ($parameters as $parameterReflection) { if ($parameterReflection->getName() === $parameter) { return reflectedParameterAttribute($parameterReflection); } } throw new LogicException( (string) message( "Parameter `%name%` doesn't exists", name: $parameter ) ); } /** * Get Parameters from a function or method reflection. */ function reflectionToParameters( ReflectionFunction|ReflectionMethod $reflection ): ParametersInterface { $hasVariadic = false; $parameters = parameters(); foreach ($reflection->getParameters() as $reflectionParameter) { try { $push = reflectedParameterAttribute($reflectionParameter); } catch (AttributeNotFoundException) { $push = new ReflectionParameterTyped($reflectionParameter); } $push = $push->parameter(); if ($reflectionParameter->isDefaultValueAvailable() && $reflectionParameter->getDefaultValue() !== null && $push->default() === null ) { try { $push = $push->withDefault($reflectionParameter->getDefaultValue()); } catch (Throwable $e) { $name = $reflectionParameter->getName(); $class = $reflectionParameter->getDeclaringClass()?->getName() ?? null; $function = $reflectionParameter->getDeclaringFunction()->getName(); $caller = match (true) { $class === null => $function, default => $class . '::' . $function, }; throw new InvalidArgumentException( (string) message( 'Unable to use default value for parameter `%name%` in `%caller%`: %message%', name: $name, caller: $caller, message: $e->getMessage(), ) ); } } $withMethod = match ($reflectionParameter->isOptional()) { true => 'withOptional', default => 'withRequired', }; $parameters = $parameters->{$withMethod}( $reflectionParameter->getName(), $push ); if ($reflectionParameter->isVariadic()) { $parameters = $parameters->withIsVariadic(true); } } return $parameters; } /** * Get a return Parameter from a function or method reflection. */ function reflectionToReturn( ReflectionFunction|ReflectionMethod $reflection ): ParameterInterface { $attributes = $reflection->getAttributes(ReturnAttr::class); if ($attributes === []) { $returnType = (string) $reflection->getReturnType(); return toParameter($returnType); } /** @var ReflectionAttribute<ReturnAttr> $attribute */ $attribute = $attributes[0]; return $attribute->newInstance()->parameter(); } function reflectedParameterAttribute( ReflectionParameter $reflection, ): ParameterAttributeInterface { $isSensitive = $reflection->getAttributes(SensitiveParameter::class) !== []; $attributes = $reflection->getAttributes( ParameterAttributeInterface::class, ReflectionAttribute::IS_INSTANCEOF ); if ($attributes === []) { throw new AttributeNotFoundException( (string) message( 'No `%type%` attribute for parameter `%name%`', type: ParameterAttributeInterface::class, name: $reflection->getName() ) ); } /** @var ReflectionAttribute<ParameterAttributeInterface> $attribute */ $attribute = $attributes[0]; return $attribute->newInstance()->withIsSensitive($isSensitive); } function validated(callable $callable, mixed ...$args): mixed { // @phpstan-ignore-next-line $reflection = new ReflectionFunction($callable); try { $parameters = reflectionToParameters($reflection); $return = reflectionToReturn($reflection); $parameters(...$args); } catch (Throwable $e) { // // @infection-ignore-all throw new ParameterException( ...getExceptionArguments($e, $reflection), ); } $result = $callable(...$args); try { /** @var callable $return */ $return($result); // @phpstan-ignore-line } catch (Throwable $e) { // @infection-ignore-all throw new ReturnException( ...getExceptionArguments($e, $reflection), ); } return $return; } /** * @return array{0: string, 1: Throwable, 2: string, 3: int} */ function getExceptionArguments(Throwable $e, ReflectionFunction $reflection): array { // @infection-ignore-all $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; $function = $reflection->getName(); $message = (string) message( '`%actor%` %exception% ? %message%', exception: $e::class, actor: $function, message: $e->getMessage(), ); // @infection-ignore-all return [ $message, $e, $caller['file'] ?? 'na', $caller['line'] ?? 0, ]; } /** * Returns an string representation of a user provided value. * * Will return " `value`" with leading space and wrap in backtick. * If the value is empty or sensitive, will return an empty string. * * @return string A markdown formatted string. */ function valMd(mixed $value, bool $isSensitive = false): string { if ($isSensitive) { return ''; } if ($value === null) { $value = 'null'; } if (! is_scalar($value)) { $value = var_export($value, true); } else { $value = strval($value); } return $value === '' ? '' : " `{$value}`"; }