Passed
Push — scalar-object ( c5e998...b04816 )
by Akihito
03:16
created

ScalarParam::getProp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 3
dl 0
loc 15
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use BEAR\Resource\Exception\ParameterException;
8
use Ray\Di\Di\Named;
9
use Ray\Di\Di\Qualifier;
10
use Ray\Di\InjectorInterface;
11
use ReflectionClass;
12
use ReflectionNamedType;
13
14
use function array_shift;
15
use function array_unshift;
16
use function assert;
17
use function class_exists;
18
use function ltrim;
19
use function preg_replace;
20
use function strtolower;
21
22
/**
23
 * @psalm-type DependencyMeta = array{0:class-string|'', 1:string}
24
 * @psalm-type DependencyMetas = list<DependencyMeta>
25
 */
26
final class ScalarParam implements ParamInterface
27
{
28
    /** @var DependencyMetas */
0 ignored issues
show
Bug introduced by
The type BEAR\Resource\DependencyMetas 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...
29
    private array $dependenciesMetas;
30
31
    /** @param class-string $typeName */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
32
    public function __construct(
33
        private string $typeName,
34
    ) {
35
        // Retrieve the dependency metadata to construct the object later
36
        $this->dependenciesMetas = $this->getDependenciesMetas();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getDependenciesMetas() of type array is incompatible with the declared type BEAR\Resource\DependencyMetas of property $dependenciesMetas.

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...
37
    }
38
39
    /**
40
     * {@inheritDoc}
41
     *
42
     * @param array<string, mixed> $query
43
     *
44
     * @return object
45
     */
46
    public function __invoke(string $varName, array $query, InjectorInterface $injector): object
47
    {
48
        /** @psalm-suppress MixedAssignment */
49
        $arg1 = $this->getProp($varName, $query, $injector);
50
        $args = [];
51
        foreach ($this->dependenciesMetas as $meta) {
52
            /** @psalm-suppress MixedAssignment */
53
            [$className, $qualifier] = $meta;
54
            /** @psalm-suppress MixedAssignment, ArgumentTypeCoercion, MixedArgument */
55
            $args[] = $injector->getInstance($className, $qualifier);
56
        }
57
58
        array_unshift($args, $arg1);
59
60
        /** @psalm-suppress InvalidStringClass, MixedMethodCall */
61
        return new $this->typeName(...$args);
62
    }
63
64
    /**
65
     * @return DependencyMetas
66
     *
67
     * @psalm-suppress MoreSpecificReturnType, LessSpecificReturnStatement
68
     */
69
    public function getDependenciesMetas(): array
70
    {
71
        $class = new ReflectionClass($this->typeName);
72
        $const = $class->getConstructor();
73
        if (! $const) {
74
            return []; // No constructor to resolve
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type BEAR\Resource\DependencyMetas.
Loading history...
75
        }
76
77
        $args = $const->getParameters();
78
        $params = [];
79
80
        // Skip the first parameter (scalar value)
81
        array_shift($args);
82
83
        foreach ($args as $arg) {
84
            $type = $arg->getType();
85
            assert($type instanceof ReflectionNamedType || $type === null, 'Expected a named type or null');
86
            $typeName = $type ? $type->getName() : '';
87
            $attributes = $arg->getAttributes();
88
            foreach ($attributes as $attribute) {
89
                if ($attribute->getName() === Named::class) {
90
                    $named = $attribute->newInstance();
91
                    /** @var DependencyMeta $dependencyMeta */
92
                    $dependencyMeta = [$typeName, $named->value];
93
                    $params[] = $dependencyMeta;
94
                    continue;
95
                }
96
97
                $class = $attribute->getName();
98
                assert(class_exists($class));
99
                $attributeReflection = new ReflectionClass($class);
100
                // Check if this attribute has the Qualifier attribute
101
                $qualifierAttributes = $attributeReflection->getAttributes(Qualifier::class);
102
                if (! $qualifierAttributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $qualifierAttributes 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...
103
                    continue;
104
                }
105
106
                /** @var DependencyMeta $dependencyMeta */
107
                $dependencyMeta = [$typeName, $attribute->getName()];
108
109
                $params[] = $dependencyMeta;
110
            }
111
112
            $params[] = [$typeName, ''];
113
        }
114
115
        /** @var DependencyMetas $params */
116
        return $params;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $params returns the type BEAR\Resource\DependencyMetas which is incompatible with the type-hinted return array.
Loading history...
117
    }
118
119
    /**
120
     * @param array<string, mixed> $query
121
     *
122
     * @return mixed
123
     */
124
    private function getProp(string $varName, array $query, InjectorInterface $injector): mixed
125
    {
126
        if (isset($query[$varName])) {
127
            return $query[$varName];
128
        }
129
130
        // try camelCase variable name
131
        $snakeName = ltrim(strtolower((string) preg_replace('/[A-Z]/', '_\0', $varName)), '_');
132
        if (isset($query[$snakeName])) {
133
            return $query[$snakeName];
134
        }
135
136
        unset($injector);
137
138
        throw new ParameterException($varName);
139
    }
140
}
141