Issues (55)

src/ClassParam.php (5 issues)

1
<?php
2
3
namespace BEAR\Resource;
4
5
use BackedEnum;
6
use BEAR\Resource\Exception\ParameterEnumTypeException;
7
use BEAR\Resource\Exception\ParameterException;
8
use BEAR\Resource\Exception\ParameterInvalidEnumException;
9
use Ray\Di\InjectorInterface;
10
use ReflectionClass;
11
use ReflectionEnum;
12
use ReflectionNamedType;
13
use ReflectionParameter;
14
use UnitEnum;
15
16
use function assert;
17
use function class_exists;
18
use function enum_exists;
19
use function is_a;
20
use function is_int;
21
use function is_iterable;
22
use function is_string;
23
24
final class ClassParam implements ParamInterface
25
{
26
    private readonly string $type;
27
    private readonly bool $isDefaultAvailable;
28
    private readonly mixed $defaultValue; // @phpstan-ignore-line
29
    private readonly QueryProp $queryProp;
30
31
    public function __construct(
32
        ReflectionNamedType $type,
33
        ReflectionParameter $parameter,
34
    ) {
35
        $this->type = $type->getName();
0 ignored issues
show
The property type is declared read-only in BEAR\Resource\ClassParam.
Loading history...
36
        $this->isDefaultAvailable = $parameter->isDefaultValueAvailable();
0 ignored issues
show
The property isDefaultAvailable is declared read-only in BEAR\Resource\ClassParam.
Loading history...
37
        $this->queryProp = new QueryProp();
0 ignored issues
show
The property queryProp is declared read-only in BEAR\Resource\ClassParam.
Loading history...
38
        if (! $this->isDefaultAvailable) {
39
            return;
40
        }
41
42
        $this->defaultValue = $parameter->getDefaultValue();
0 ignored issues
show
The property defaultValue is declared read-only in BEAR\Resource\ClassParam.
Loading history...
43
    }
44
45
    /**
46
     * {@inheritDoc}
47
     */
48
    public function __invoke(string $varName, array $query, InjectorInterface $injector)
49
    {
50
        try {
51
            /** @psalm-suppress MixedAssignment */
52
            $props = $this->queryProp->getProp($varName, $query, $injector);
53
        } catch (ParameterException $e) {
54
            if ($this->isDefaultAvailable) {
55
                return $this->defaultValue;
56
            }
57
58
            throw $e;
59
        }
60
61
        assert(class_exists($this->type));
62
        $refClass = new ReflectionClass($this->type);
63
64
        if ($refClass->isEnum()) {
65
            return $this->enum($this->type, $props, $varName);
66
        }
67
68
        assert(is_iterable($props));
69
70
        $hasConstructor = (bool) $refClass->getConstructor();
71
        if ($hasConstructor) {
72
            /** @psalm-suppress MixedMethodCall */
73
            return new $this->type(...$props);
74
        }
75
76
        /** @psalm-suppress MixedMethodCall */
77
        $obj = new $this->type();
78
        /** @psalm-suppress MixedAssignment */
79
        foreach ($props as $propName => $propValue) {
80
            $obj->{$propName} = $propValue;
81
        }
82
83
        return $obj;
84
    }
85
86
    /**
87
     * @param class-string $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
88
     *
89
     * @psalm-suppress MixedArgument
90
     */
91
    private function enum(string $type, mixed $props, string $varName): mixed
92
    {
93
        /** @var class-string<UnitEnum> $type */
94
        $refEnum = new ReflectionEnum($type);
95
        assert(enum_exists($type));
96
97
        if (! $refEnum->isBacked()) {
98
            throw new NotBackedEnumException($type);
99
        }
100
101
        assert(is_a($type, BackedEnum::class, true));
102
        if (! (is_int($props) || is_string($props))) {
103
            throw new ParameterEnumTypeException($varName);
104
        }
105
106
        /**  @psalm-suppress MixedAssignment */
107
        $value = $type::tryFrom($props);
108
        if ($value === null) {
109
            throw new ParameterInvalidEnumException($varName);
110
        }
111
112
        return $value;
113
    }
114
}
115