Passed
Pull Request — master (#69)
by
unknown
14:53 queued 12:08
created

ResolvingState::resolveParameterByClasses()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 11
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 18
ccs 0
cts 12
cp 0
crap 30
rs 9.6111
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Injector;
6
7
use Generator;
8
use ReflectionFunctionAbstract;
9
10
/**
11
 * Intermediate arguments resolving data to pass around until resolving is finished.
12
 *
13
 * @internal
14
 */
15
final class ResolvingState
16
{
17
    /**
18
     * @psalm-var array<int, object>
19
     */
20
    private array $numericArguments = [];
21
22
    /**
23
     * @psalm-var array<string, mixed>
24
     */
25
    private array $namedArguments = [];
26
27
    private bool $shouldPushTrailingArguments;
28
29
    /**
30
     * @psalm-var list<mixed>
31
     */
32
    private array $resolvedValues = [];
33
34
    /**
35
     * @param ReflectionFunctionAbstract $reflection Function reflection.
36
     * @param array $arguments User arguments.
37
     */
38 51
    public function __construct(private ReflectionFunctionAbstract $reflection, array $arguments)
39
    {
40 51
        $this->shouldPushTrailingArguments = !$reflection->isInternal();
41 51
        $this->sortArguments($arguments);
42
    }
43
44
    public function hasNamedArgument(string $name): bool
45
    {
46
        return array_key_exists($name, $this->namedArguments);
47
    }
48
49
    /**
50
     * @param bool $condition If true then trailing arguments will not be passed.
51
     */
52 40
    public function disablePushTrailingArguments(bool $condition): void
53
    {
54 40
        $this->shouldPushTrailingArguments = $this->shouldPushTrailingArguments && !$condition;
55
    }
56
57 34
    public function addResolvedValue(mixed &$value): void
58
    {
59
        /** @psalm-suppress UnsupportedReferenceUsage */
60 34
        $this->resolvedValues[] = &$value;
61
    }
62
63 40
    public function resolveParameterByName(string $name, bool $variadic): bool
64
    {
65 40
        if (!array_key_exists($name, $this->namedArguments)) {
66 34
            return false;
67
        }
68 13
        if ($variadic && is_array($this->namedArguments[$name])) {
69 2
            array_walk($this->namedArguments[$name], [$this, 'addResolvedValue']);
70
        } else {
71 12
            $this->addResolvedValue($this->namedArguments[$name]);
72
        }
73 13
        return true;
74
    }
75
76
    /**
77
     * @psalm-param class-string|null $className
78
     */
79 28
    public function resolveParameterByClass(?string $className, bool $variadic): bool
80
    {
81 28
        $generator = $this->pullNumericArgument($className);
82 28
        if (!$variadic) {
83 24
            if (!$generator->valid()) {
84 21
                return false;
85
            }
86 7
            $value = $generator->current();
87 7
            $this->addResolvedValue($value);
88 7
            return true;
89
        }
90 7
        foreach ($generator as &$value) {
91 5
            $this->addResolvedValue($value);
92
        }
93 7
        return true;
94
    }
95
96
    /**
97
     * Resolve parameter using type intersection rules.
98
     *
99
     * @psalm-param array<int, class-string> $classNames
100
     */
101
    public function resolveParameterByClasses(array $classNames, bool $variadic): bool
102
    {
103
        $resolved = false;
104
        foreach ($this->numericArguments as $key => &$argument) {
105
            foreach ($classNames as $class) {
106
                if (!$argument instanceof $class) {
107
                    continue 2;
108
                }
109
            }
110
            unset($this->numericArguments[$key]);
111
            $this->addResolvedValue($argument);
112
            if (!$variadic) {
113
                return true;
114
            }
115
            $resolved = true;
116
        }
117
118
        return $resolved;
119
    }
120
121 42
    public function getResolvedValues(): array
122
    {
123 42
        return $this->shouldPushTrailingArguments
124 27
            ? [...$this->resolvedValues, ...$this->numericArguments]
125 42
            : $this->resolvedValues;
126
    }
127
128
    /**
129
     * @psalm-param class-string|null $className
130
     *
131
     * @psalm-return Generator<int, object, mixed, void>
132
     */
133 28
    private function &pullNumericArgument(?string $className): Generator
134
    {
135 28
        foreach ($this->numericArguments as $key => &$value) {
136 12
            if ($className === null || $value instanceof $className) {
137 11
                unset($this->numericArguments[$key]);
138 11
                yield $value;
139
            }
140
        }
141
    }
142
143
    /**
144
     * @throws InvalidArgumentException
145
     */
146 51
    private function sortArguments(array $arguments): void
147
    {
148 51
        foreach ($arguments as $key => &$value) {
149 28
            if (is_int($key)) {
150 20
                if (!is_object($value)) {
151 3
                    throw new InvalidArgumentException($this->reflection, (string)$key);
152
                }
153
                /** @psalm-suppress UnsupportedReferenceUsage */
154 17
                $this->numericArguments[] = &$value;
155
            } else {
156 15
                $this->namedArguments[$key] = &$value;
157
            }
158
        }
159
    }
160
}
161