Passed
Pull Request — 1.x (#321)
by Akihito
02:42
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\Exception\InputClassCreateException;
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
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_array;
20
21
final class InputParamFactory
22
{
23
    public function __construct(
24
        private readonly InputParamEnumHandler $enumHandler,
25
        private readonly InputParamObjectHandler $objectHandler,
26
        private readonly InputAttributeIterator $inputIterator,
27
    ) {
28
    }
29
30
    /** @param array<string, mixed> $query */
31
    public function create(
32
        string $type,
33
        string $varName,
34
        array $query,
35
        InjectorInterface $injector,
36
        ReflectionParameter $parameter,
37
        bool $isDefaultAvailable,
38
        mixed $defaultValue,
39
    ): mixed {
40
        /** @var class-string $type */
41
        /** @psalm-suppress MixedArgument, ArgumentTypeCoercion */
42
        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

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

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