Passed
Pull Request — 1.x (#321)
by Akihito
02:49
created

InputParamFactory::create()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 49
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 9
nop 7
dl 0
loc 49
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\Input;
6
7
use BEAR\Resource\Annotation\Input;
8
use BEAR\Resource\Exception\InputClassCreateException;
9
use BEAR\Resource\Exception\ParameterException;
10
use InvalidArgumentException;
11
use Ray\Di\InjectorInterface;
12
use ReflectionClass;
13
use ReflectionMethod;
14
use ReflectionParameter;
15
use Throwable;
16
17
use function assert;
18
use function class_exists;
19
use function count;
20
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...
21
use function is_array;
22
23
final class InputParamFactory
24
{
25
    public function __construct(
26
        private readonly InputParamEnumHandler $enumHandler,
27
        private readonly InputParamObjectHandler $objectHandler,
28
    ) {
29
    }
30
31
    /** @param array<string, mixed> $query */
32
    public function create(
33
        string $type,
34
        string $varName,
35
        array $query,
36
        InjectorInterface $injector,
37
        ReflectionParameter $parameter,
38
        bool $isDefaultAvailable,
39
        mixed $defaultValue,
40
    ): mixed {
41
        /** @var class-string $type */
42
        /** @psalm-suppress MixedArgument, ArgumentTypeCoercion */
43
        assert(class_exists($type) || 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

43
        assert(class_exists($type) || /** @scrutinizer ignore-call */ enum_exists($type));
Loading history...
44
        /** @psalm-suppress ArgumentTypeCoercion */
45
        $refClass = new ReflectionClass($type);
46
47
        // Handle enums
48
        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

48
        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...
49
            return $this->enumHandler->createEnum($type, $varName, $query, $isDefaultAvailable, $defaultValue);
50
        }
51
52
        $constructor = $refClass->getConstructor();
53
        if ($constructor === null) {
54
            return $this->objectHandler->createWithoutConstructor($type, $varName, $query, $isDefaultAvailable, $defaultValue);
55
        }
56
57
        // Check if this parameter has #[Input] attribute
58
        $inputAttr = $this->getInputAttribute($parameter);
59
60
        // If no #[Input] attribute, use ClassParam behavior
61
        if ($inputAttr === null) {
62
            return $this->createFromStructuredData($refClass, $constructor, $varName, $query, $injector, $type, $isDefaultAvailable, $defaultValue);
63
        }
64
65
        // If #[Input] has key specified, use structured data approach
66
        if ($inputAttr->key !== null) {
67
            return $this->createFromStructuredData($refClass, $constructor, $inputAttr->key, $query, $injector, $type, $isDefaultAvailable, $defaultValue);
68
        }
69
70
        try {
71
            $constructorArgs = $this->objectHandler->getConstructorArgs($constructor, $query, $injector, $type);
72
73
            /** @psalm-suppress MixedArgumentTypeCoercion */
74
            return $refClass->newInstanceArgs($constructorArgs);
75
        } catch (Throwable $e) {
76
            if ($e instanceof InvalidArgumentException) {
77
                throw $e;
78
            }
79
80
            throw new InputClassCreateException($type, 0, $e);
81
        }
82
    }
83
84
    private function getInputAttribute(ReflectionParameter $param): Input|null
85
    {
86
        $attributes = $param->getAttributes(Input::class);
87
        if (count($attributes) === 0) {
88
            return null;
89
        }
90
91
        return $attributes[0]->newInstance();
92
    }
93
94
    /**
95
     * @param ReflectionClass<object> $refClass
96
     * @param array<string, mixed>    $query
97
     */
98
    private function createFromStructuredData(
99
        ReflectionClass $refClass,
100
        ReflectionMethod $constructor,
101
        string $key,
102
        array $query,
103
        InjectorInterface $injector,
104
        string $type,
105
        bool $isDefaultAvailable,
106
        mixed $defaultValue,
107
    ): mixed {
108
        if (! isset($query[$key])) {
109
            if ($isDefaultAvailable) {
110
                return $defaultValue;
111
            }
112
113
            throw new ParameterException("Required key '{$key}' not found for {$type}");
114
        }
115
116
        $data = $query[$key];
117
        if (! is_array($data)) {
118
            throw new ParameterException("Data under key '{$key}' must be an array for {$type}");
119
        }
120
121
        /** @var array<string, mixed> $data */
122
        return $this->objectHandler->newInstance($constructor, $data, $injector, $key, $refClass, $type);
123
    }
124
}
125