Completed
Push — master ( 6366df...fbe022 )
by Kirill
23s queued 19s
created

NamedArgumentsInstantiator::resolveParameter()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 4
eloc 19
c 5
b 0
f 0
nc 6
nop 3
dl 0
loc 30
rs 9.6333
1
<?php
2
3
/**
4
 * This file is part of Spiral Framework package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Attributes\Internal\Instantiator;
13
14
use Spiral\Attributes\Internal\Exception;
15
16
/**
17
 * @internal NamedArgumentsInstantiator is an internal library class, please do not use it in your code.
18
 * @psalm-internal Spiral\Attributes
19
 */
20
final class NamedArgumentsInstantiator extends Instantiator
21
{
22
    /**
23
     * @var string
24
     */
25
    private const ERROR_ARGUMENT_NOT_PASSED = '%s::__construct(): Argument #%d ($%s) not passed';
26
27
    /**
28
     * @var string
29
     */
30
    private const ERROR_UNKNOWN_ARGUMENT = 'Unknown named parameter $%s';
31
32
    /**
33
     * {@inheritDoc}
34
     */
35
    public function instantiate(\ReflectionClass $attr, array $arguments, \Reflector $context = null): object
36
    {
37
        if ($this->isNamedArgumentsSupported()) {
38
            try {
39
                return $attr->newInstanceArgs($arguments);
40
            } catch (\Throwable $e) {
41
                throw Exception::withLocation($e, $attr->getFileName(), $attr->getStartLine());
42
            }
43
        }
44
45
        $constructor = $this->getConstructor($attr);
46
47
        if ($constructor === null) {
48
            return $attr->newInstanceWithoutConstructor();
49
        }
50
51
        return $attr->newInstanceArgs(
52
            $this->resolveParameters($attr, $constructor, $arguments)
53
        );
54
    }
55
56
    /**
57
     * @return bool
58
     */
59
    private function isNamedArgumentsSupported(): bool
60
    {
61
        return \version_compare(\PHP_VERSION, '8.0') >= 0;
62
    }
63
64
    /**
65
     * @param \ReflectionClass $ctx
66
     * @param \ReflectionMethod $constructor
67
     * @param array $arguments
68
     * @return array
69
     * @throws \Throwable
70
     */
71
    private function resolveParameters(\ReflectionClass $ctx, \ReflectionMethod $constructor, array $arguments): array
72
    {
73
        $passed = [];
74
75
        try {
76
            foreach ($constructor->getParameters() as $parameter) {
77
                $passed[] = $this->resolveParameter($ctx, $parameter, $arguments);
78
            }
79
80
            if (\count($arguments)) {
81
                $message = \sprintf(self::ERROR_UNKNOWN_ARGUMENT, \array_key_first($arguments));
82
                throw new \BadMethodCallException($message);
83
            }
84
        } catch (\Throwable $e) {
85
            throw Exception::withLocation($e, $constructor->getFileName(), $constructor->getStartLine());
86
        }
87
88
        return $passed;
89
    }
90
91
    /**
92
     * @param \ReflectionClass $ctx
93
     * @param \ReflectionParameter $param
94
     * @param array $arguments
95
     * @return mixed
96
     * @throws \Throwable
97
     */
98
    private function resolveParameter(\ReflectionClass $ctx, \ReflectionParameter $param, array &$arguments)
99
    {
100
        switch (true) {
101
            case \array_key_exists($param->getName(), $arguments):
102
                try {
103
                    return $arguments[$param->getName()];
104
                } finally {
105
                    unset($arguments[$param->getName()]);
106
                }
107
                // no actual falling through
108
109
            case \array_key_exists($param->getPosition(), $arguments):
110
                try {
111
                    return $arguments[$param->getPosition()];
112
                } finally {
113
                    unset($arguments[$param->getPosition()]);
114
                }
115
                // no actual falling through
116
117
            case $param->isDefaultValueAvailable():
118
                return $param->getDefaultValue();
119
120
            default:
121
                $message = \vsprintf(self::ERROR_ARGUMENT_NOT_PASSED, [
122
                    $ctx->getName(),
123
                    $param->getPosition() + 1,
124
                    $param->getName(),
125
                ]);
126
127
                throw new \ArgumentCountError($message);
128
        }
129
    }
130
}
131