ResolvingState   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 75%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 55
c 4
b 0
f 0
dl 0
loc 150
ccs 42
cts 56
cp 0.75
rs 10
wmc 28

10 Methods

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