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

InputParamFactory   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 100
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 31
dl 0
loc 100
rs 10
c 0
b 0
f 0
wmc 15

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getInputAttribute() 0 8 2
A __construct() 0 4 1
A createFromStructuredData() 0 25 4
B create() 0 49 8
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\ParameterException;
9
use InvalidArgumentException;
10
use Ray\Di\InjectorInterface;
11
use ReflectionClass;
12
use ReflectionMethod;
13
use ReflectionParameter;
14
use Throwable;
15
use function assert;
16
use function class_exists;
17
use function count;
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_array;
20
21
final class InputParamFactory
22
{
23
    public function __construct(
24
        private readonly InputParamEnumHandler $enumHandler,
25
        private readonly InputParamObjectHandler $objectHandler,
26
    ) {
27
    }
28
29
    /** @param array<string, mixed> $query */
30
    public function create(
31
        string $type,
32
        string $varName,
33
        array $query,
34
        InjectorInterface $injector,
35
        ReflectionParameter $parameter,
36
        bool $isDefaultAvailable,
37
        mixed $defaultValue,
38
    ): mixed {
39
        /** @var class-string $type */
40
        /** @psalm-suppress MixedArgument, ArgumentTypeCoercion */
41
        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

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

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