Passed
Push — feature/legacy-method-qualifie... ( 284fc0 )
by Akihito
02:37
created

Name::getName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 8
c 2
b 0
f 0
dl 0
loc 14
rs 10
cc 3
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use Ray\Di\Di\Named;
8
use Ray\Di\Di\Qualifier;
9
use ReflectionAttribute;
10
use ReflectionClass;
11
use ReflectionException;
12
use ReflectionMethod;
13
use ReflectionParameter;
14
15
use function class_exists;
16
use function count;
17
use function is_string;
18
use function preg_match;
19
20
/**
21
 * @psalm-import-type ParameterNameMapping from Types
22
 */
23
final class Name
24
{
25
    /**
26
     * 'Unnamed' name
27
     */
28
    public const ANY = '';
29
30
    private string $name = '';
31
32
    /**
33
     * Named database
34
     *
35
     * format: array<varName, NamedName>
36
     *
37
     * @var ParameterNameMapping
0 ignored issues
show
Bug introduced by
The type Ray\Di\ParameterNameMapping was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
     */
39
    private array $names = [];
40
41
    /**
42
     * @param string|ParameterNameMapping $name
43
     */
44
    public function __construct(string|array $name)
45
    {
46
        if (is_string($name)) {
0 ignored issues
show
introduced by
The condition is_string($name) is always false.
Loading history...
47
            $this->setName($name);
48
49
            return;
50
        }
51
52
        $this->names = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name of type array is incompatible with the declared type Ray\Di\ParameterNameMapping of property $names.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
53
    }
54
55
    /**
56
     * Create instance from PHP8 attributes
57
     *
58
     * psalm does not know ReflectionAttribute?? PHPStan produces no type error here.
59
     */
60
    public static function withAttributes(ReflectionMethod $method): ?self
61
    {
62
        $params = $method->getParameters();
63
        $names = [];
64
        foreach ($params as $param) {
65
            /** @var array<ReflectionAttribute> $attributes */
66
            $attributes = $param->getAttributes();
67
            if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
68
                $name = self::getName($attributes);
69
                $names[$param->name] = $name;
70
            }
71
        }
72
73
        // Legacy: Infer qualifier from method-level attribute for single-parameter methods
74
        if ($names === []) {
75
            /** @psalm-suppress DeprecatedClass */
76
            $inferredQualifier = LegacyMethodQualifierInference::inferQualifier($method);
77
            if ($inferredQualifier !== '' && count($params) === 1) {
78
                $names[$params[0]->name] = $inferredQualifier;
79
            }
80
        }
81
82
        if ($names !== []) {
83
            return new self($names);
84
        }
85
86
        return null;
87
    }
88
89
    /**
90
     * @param non-empty-array<ReflectionAttribute> $attributes
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-array<ReflectionAttribute> at position 0 could not be parsed: Unknown type name 'non-empty-array' at position 0 in non-empty-array<ReflectionAttribute>.
Loading history...
91
     *
92
     * @throws ReflectionException
93
     *
94
     * @psalm-suppress MixedAssignment
95
     * @psalm-suppress MixedArgument
96
     */
97
    private static function getName(array $attributes): string
98
    {
99
        $refAttribute = $attributes[0];
100
        $attribute = $refAttribute->newInstance();
101
        if ($attribute instanceof Named) {
102
            return $attribute->value;
103
        }
104
105
        $isQualifier = (bool) (new ReflectionClass($attribute))->getAttributes(Qualifier::class);
106
        if ($isQualifier) {
107
            return $attribute::class;
108
        }
109
110
        return '';
111
    }
112
113
    public function __invoke(ReflectionParameter $parameter): string
114
    {
115
        // single variable named binding
116
        if ($this->name) {
117
            return $this->name;
118
        }
119
120
        $parameterName = $parameter->name;
121
122
        // multiple variable named binding
123
        return $this->names[$parameterName] ?? $this->names[self::ANY] ?? self::ANY;
124
    }
125
126
    private function setName(string $name): void
127
    {
128
        // annotation
129
        if (class_exists($name, false)) {
130
            $this->name = $name;
131
132
            return;
133
        }
134
135
        // single name
136
        // @Named(name)
137
        if ($name === self::ANY || preg_match('/^\w+$/', $name)) {
138
            $this->name = $name;
139
140
            return;
141
        }
142
143
        // name list (backward compatibility)
144
        // @Named(varName1=name1, varName2=name2) or toConstructor string format
145
        /** @psalm-suppress DeprecatedClass */
146
        $this->names = LegacyStringParser::parse($name);
0 ignored issues
show
Documentation Bug introduced by
It seems like Ray\Di\LegacyStringParser::parse($name) of type array or array is incompatible with the declared type Ray\Di\ParameterNameMapping of property $names.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
147
    }
148
}
149