Passed
Pull Request — master (#64)
by Aleksei
07:27 queued 05:18
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
    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 50
    public function __construct(ReflectionFunctionAbstract $reflection, array $arguments)
41
    {
42 50
        $this->reflection = $reflection;
43 50
        $this->shouldPushTrailingArguments = !$reflection->isInternal();
44 50
        $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 39
    public function disablePushTrailingArguments(bool $condition): void
56
    {
57 39
        $this->shouldPushTrailingArguments = $this->shouldPushTrailingArguments && !$condition;
58
    }
59
60
    /**
61
     * @param mixed $value
62
     */
63 33
    public function addResolvedValue(&$value): void
64
    {
65 33
        $this->resolvedValues[] = &$value;
66
    }
67
68 39
    public function resolveParameterByName(string $name, bool $variadic): bool
69
    {
70 39
        if (!array_key_exists($name, $this->namedArguments)) {
71 33
            return false;
72
        }
73 12
        if ($variadic && is_array($this->namedArguments[$name])) {
74 2
            array_walk($this->namedArguments[$name], [$this, 'addResolvedValue']);
75
        } else {
76 11
            $this->addResolvedValue($this->namedArguments[$name]);
77
        }
78 12
        return true;
79
    }
80
81
    /**
82
     * @psalm-param class-string|null $className
83
     */
84 27
    public function resolveParameterByClass(?string $className, bool $variadic): bool
85
    {
86 27
        $generator = $this->pullNumericArgument($className);
87 27
        if (!$variadic) {
88 23
            if (!$generator->valid()) {
89 20
                return false;
90
            }
91 7
            $value = $generator->current();
92 7
            $this->addResolvedValue($value);
93 7
            return true;
94
        }
95 7
        foreach ($generator as &$value) {
96 5
            $this->addResolvedValue($value);
97
        }
98 7
        return true;
99
    }
100
101
    /**
102
     * Resolve parameter using type intersection rules.
103
     *
104
     * @psalm-param array<int, class-string> $classNames
105
     */
106
    public function resolveParameterByClasses(array $classNames, bool $variadic): bool
107
    {
108
        $resolved = false;
109
        foreach ($this->numericArguments as $key => &$argument) {
110
            foreach ($classNames as $class) {
111
                if (!$argument instanceof $class) {
112
                    continue 2;
113
                }
114
            }
115
            unset($this->numericArguments[$key]);
116
            $this->addResolvedValue($argument);
117
            if (!$variadic) {
118
                return true;
119
            }
120
            $resolved = true;
121
        }
122
123
        return $resolved;
124
    }
125
126 41
    public function getResolvedValues(): array
127
    {
128 41
        return $this->shouldPushTrailingArguments
129 26
            ? [...$this->resolvedValues, ...$this->numericArguments]
130 41
            : $this->resolvedValues;
131
    }
132
133
    /**
134
     * @psalm-param class-string|null $className
135
     *
136
     * @psalm-return Generator<int, object, mixed, void>
137
     */
138 27
    private function &pullNumericArgument(?string $className): Generator
139
    {
140 27
        foreach ($this->numericArguments as $key => &$value) {
141 12
            if ($className === null || $value instanceof $className) {
142 11
                unset($this->numericArguments[$key]);
143 11
                yield $value;
144
            }
145
        }
146
    }
147
148
    /**
149
     * @param array $arguments
150
     *
151
     * @throws InvalidArgumentException
152
     */
153 50
    private function sortArguments(array $arguments): void
154
    {
155 50
        foreach ($arguments as $key => &$value) {
156 27
            if (is_int($key)) {
157 20
                if (!is_object($value)) {
158 3
                    throw new InvalidArgumentException($this->reflection, (string)$key);
159
                }
160 17
                $this->numericArguments[] = &$value;
161
            } else {
162 14
                $this->namedArguments[$key] = &$value;
163
            }
164
        }
165
    }
166
}
167