Completed
Push — 1.x ( 9d9002...75031f )
by Akihito
21s queued 16s
created

ClassParam   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 44
c 7
b 0
f 0
dl 0
loc 105
rs 10
wmc 16

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A __invoke() 0 36 6
A getProps() 0 15 3
A enum() 0 22 5
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;
0 ignored issues
show
Bug introduced by
The type ReflectionEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use ReflectionNamedType;
13
use ReflectionParameter;
14
use UnitEnum;
15
16
use function assert;
17
use function class_exists;
18
use function enum_exists;
0 ignored issues
show
introduced by
The function enum_exists was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
19
use function is_a;
20
use function is_int;
21
use function is_iterable;
22
use function is_string;
23
use function ltrim;
24
use function preg_replace;
25
use function strtolower;
26
27
final class ClassParam implements ParamInterface
28
{
29
    private readonly string $type;
30
    private readonly bool $isDefaultAvailable;
31
    private readonly mixed $defaultValue; // @phpstan-ignore-line
32
33
    public function __construct(
34
        ReflectionNamedType $type,
35
        ReflectionParameter $parameter,
36
    ) {
37
        $this->type = $type->getName();
0 ignored issues
show
Bug introduced by
The property type is declared read-only in BEAR\Resource\ClassParam.
Loading history...
38
        $this->isDefaultAvailable = $parameter->isDefaultValueAvailable();
0 ignored issues
show
Bug introduced by
The property isDefaultAvailable is declared read-only in BEAR\Resource\ClassParam.
Loading history...
39
        if (! $this->isDefaultAvailable) {
40
            return;
41
        }
42
43
        $this->defaultValue = $parameter->getDefaultValue();
0 ignored issues
show
Bug introduced by
The property defaultValue is declared read-only in BEAR\Resource\ClassParam.
Loading history...
44
    }
45
46
    /**
47
     * {@inheritDoc}
48
     */
49
    public function __invoke(string $varName, array $query, InjectorInterface $injector)
50
    {
51
        try {
52
            /** @psalm-suppress MixedAssignment */
53
            $props = $this->getProps($varName, $query, $injector);
54
        } catch (ParameterException $e) {
55
            if ($this->isDefaultAvailable) {
56
                return $this->defaultValue;
57
            }
58
59
            throw $e;
60
        }
61
62
        assert(class_exists($this->type));
63
        $refClass = (new ReflectionClass($this->type));
64
65
        if ($refClass->isEnum()) {
0 ignored issues
show
Bug introduced by
The method isEnum() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
        if ($refClass->/** @scrutinizer ignore-call */ isEnum()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
66
            return $this->enum($this->type, $props, $varName);
67
        }
68
69
        assert(is_iterable($props));
70
71
        $hasConstructor = (bool) $refClass->getConstructor();
72
        if ($hasConstructor) {
73
            /** @psalm-suppress MixedMethodCall */
74
            return new $this->type(...$props);
75
        }
76
77
        /** @psalm-suppress MixedMethodCall */
78
        $obj = new $this->type();
79
        /** @psalm-suppress MixedAssignment */
80
        foreach ($props as $propName => $propValue) {
81
            $obj->{$propName} = $propValue;
82
        }
83
84
        return $obj;
85
    }
86
87
    /** @param array<string, mixed> $query */
88
    private function getProps(string $varName, array $query, InjectorInterface $injector): mixed
89
    {
90
        if (isset($query[$varName])) {
91
            return $query[$varName];
92
        }
93
94
        // try camelCase variable name
95
        $snakeName = ltrim(strtolower((string) preg_replace('/[A-Z]/', '_\0', $varName)), '_');
96
        if (isset($query[$snakeName])) {
97
            return $query[$snakeName];
98
        }
99
100
        unset($injector);
101
102
        throw new ParameterException($varName);
103
    }
104
105
    /**
106
     * @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...
107
     *
108
     * @psalm-suppress MixedArgument
109
     */
110
    private function enum(string $type, mixed $props, string $varName): mixed
111
    {
112
        /** @var class-string<UnitEnum> $type */
113
        $refEnum = new ReflectionEnum($type);
114
        assert(enum_exists($type));
0 ignored issues
show
Bug introduced by
The function enum_exists was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
        assert(/** @scrutinizer ignore-call */ enum_exists($type));
Loading history...
115
116
        if (! $refEnum->isBacked()) {
117
            throw new NotBackedEnumException($type);
118
        }
119
120
        assert(is_a($type, BackedEnum::class, true));
121
        if (! (is_int($props) || is_string($props))) {
122
            throw new ParameterEnumTypeException($varName);
123
        }
124
125
        /**  @psalm-suppress MixedAssignment */
126
        $value = $type::tryFrom($props);
127
        if ($value === null) {
128
            throw new ParameterInvalidEnumException($varName);
129
        }
130
131
        return $value;
132
    }
133
}
134