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

InputParamFactory::createFromStructuredData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 8
dl 0
loc 25
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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