<?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 Chevere\Parameter\Interfaces\ParameterInterface;
use Chevere\Parameter\Interfaces\ReflectionParameterTypedInterface;
use LogicException;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;
use Throwable;
use TypeError;
use function Chevere\Message\message;
final class ReflectionParameterTyped implements ReflectionParameterTypedInterface
{
private ReflectionUnionType|ReflectionNamedType|null $type;
private ParameterInterface $parameter;
public function __construct(
private ReflectionParameter $reflection
) {
$this->type = $this->getType();
$parameter = $this->getParameter();
try {
$attribute = reflectedParameterAttribute($reflection);
} catch (Throwable) {
// do nothing
}
if (isset($attribute, $this->type)) {
$typeHint = $this->getTypeHint($this->type);
$attrHint = $attribute->parameter()->type()->typeHinting();
if ($typeHint !== $attrHint) {
throw new TypeError(
(string) message(
'Parameter $%name% of type %type% is not compatible with %attr% attribute',
name: $reflection->getName(),
type: $typeHint,
attr: $attribute->parameter()::class
)
);
}
$parameter = $attribute->parameter();
}
if ($this->reflection->isDefaultValueAvailable()
&& $this->reflection->getDefaultValue() !== null
) {
/** @var ParameterInterface $parameter */
$parameter = $parameter
->withDefault(
$this->reflection->getDefaultValue()
);
}
$this->parameter = $parameter;
}
public function parameter(): ParameterInterface
{
return $this->parameter;
}
private function getParameter(): ParameterInterface
{
if ($this->type instanceof ReflectionUnionType) {
$types = [];
foreach ($this->type->getTypes() as $type) {
if ($type instanceof ReflectionIntersectionType) {
continue;
}
$types[] = $type->getName();
}
return toUnionParameter(...$types);
} elseif ($this->type !== null) {
return toParameter($this->getTypeHint($this->type));
}
return toParameter('mixed');
}
private function getType(): ReflectionNamedType|ReflectionUnionType|null
{
$reflection = $this->reflection->getType();
if ($reflection === null) {
return null;
}
if ($reflection instanceof ReflectionNamedType || $reflection instanceof ReflectionUnionType) {
return $reflection;
}
$name = '$' . $this->reflection->getName();
$type = $this->getReflectionType($reflection);
throw new LogicException(
(string) message(
'Parameter %name% of type %type% is not supported',
name: $name,
type: $type
)
);
}
private function getTypeHint(object $reflection): string
{
if (method_exists($reflection, 'getName')) {
return $reflection->getName();
}
if ($reflection instanceof ReflectionUnionType) {
$types = [];
foreach ($reflection->getTypes() as $type) {
$types[] = $this->getTypeHint($type);
}
return implode('|', $types);
}
return 'mixed'; // @codeCoverageIgnore
}
/**
* @infection-ignore-all
*/
private function getReflectionType(mixed $reflectionType): string
{
return match (true) {
$reflectionType instanceof ReflectionUnionType => 'union',
$reflectionType instanceof ReflectionIntersectionType => 'intersection',
default => 'unknown',
};
}
}
|