Test Failed
Pull Request — master (#941)
by Aleksei
09:19
created

StateBinder::makeConfigFromArray()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 15
rs 8.0555
ccs 0
cts 0
cp 0
cc 9
nc 4
nop 2
crap 90
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Core\Internal\Config;
6
7
use Exception;
8
use InvalidArgumentException;
9
use Spiral\Core\BinderInterface;
10
use Spiral\Core\Config\Alias;
11
use Spiral\Core\Config\Binding;
12
use Spiral\Core\Config\Factory;
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Config\Factory 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...
13
use Spiral\Core\Config\Inflector;
14
use Spiral\Core\Config\Injectable;
15
use Spiral\Core\Config\Scalar;
16
use Spiral\Core\Config\Shared;
17
use Spiral\Core\Config\DeferredFactory;
18
use Spiral\Core\Container\Autowire;
19 1111
use Spiral\Core\Container\InjectableInterface;
20
use Spiral\Core\Exception\ConfiguratorException;
21
use Spiral\Core\Exception\Container\ContainerException;
22 1111
use Spiral\Core\Internal\State;
23
use Throwable;
24
use WeakReference;
25
26
/**
27 822
 * @psalm-import-type TResolver from BinderInterface
28
 * @internal
29 822
 */
30
class StateBinder implements BinderInterface
31 313
{
32
    public function __construct(
33 313
        protected readonly State $state,
34
    ) {
35
    }
36 808
37
    /**
38
     * @param TResolver|object $resolver
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Internal\Config\TResolver 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...
39
     */
40
    public function bind(string $alias, mixed $resolver): void
41
    {
42 600
        if ($resolver instanceof Inflector && (\interface_exists($alias) || \class_exists($alias))) {
43
            $this->state->inflectors[$alias][] = $resolver;
44 600
            return;
45
        }
46 535
47
        try {
48 535
            $config = $this->makeConfig($resolver, false);
49
        } catch (\Throwable $e) {
50
            throw $this->invalidBindingException($alias, $resolver, $e);
51 564
        }
52
53
        $this->state->bindings[$alias] = $config;
54 9
    }
55
56 9
    /**
57
     * @param TResolver|object $resolver
58 9
     */
59
    public function bindSingleton(string $alias, mixed $resolver): void
60 1
    {
61
        try {
62
            $config = $this->makeConfig($resolver, true);
63 9
        } catch (\Throwable $e) {
64
            throw $this->invalidBindingException($alias, $resolver, $e);
65
        }
66 2
67
        $this->state->bindings[$alias] = $config;
68 2
    }
69
70
    public function hasInstance(string $alias): bool
71 364
    {
72
        $bindings = &$this->state->bindings;
73 364
74
        $flags = [];
75
        while ($binding = $bindings[$alias] ?? null and $binding::class === Alias::class) {
76
            //Checking alias tree
77
            if ($flags[$binding->alias] ?? false) {
78
                return $binding->alias === $alias ?: throw new Exception('Circular alias detected');
79
            }
80
81 733
            $flags[$binding->alias] = true;
82
            $alias = $binding->alias;
83
        }
84 733
85
        return isset($bindings[$alias]) && \is_object($bindings[$alias]);
86
    }
87
88
    public function removeBinding(string $alias): void
89 733
    {
90 365
        unset($this->state->bindings[$alias]);
91
    }
92
93
    public function bindInjector(string $class, string $injector): void
94 730
    {
95 730
        $this->state->bindings[$class] = new Injectable($injector);
96
        $this->state->injectors[$class] = $injector;
97 383
    }
98
99 383
    public function removeInjector(string $class): void
100
    {
101
        unset($this->state->injectors[$class]);
102
        if (!isset($this->state->bindings[$class]) || $this->state->bindings[$class]::class !== Injectable::class) {
103 725
            return;
104
        }
105 374
        unset($this->state->bindings[$class]);
106 374
    }
107
108
    public function hasInjector(string $class): bool
109
    {
110
        try {
111
            $reflection = new \ReflectionClass($class);
112
        } catch (\ReflectionException $e) {
113
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
114 374
        }
115 374
116
        if (\array_key_exists($class, $this->state->injectors)) {
117 7
            return true;
118
        }
119 7
120
        if (
121
            $reflection->implementsInterface(InjectableInterface::class)
122
            && $reflection->hasConstant('INJECTOR')
123 722
        ) {
124
            $const = $reflection->getConstant('INJECTOR');
125
            $this->bindInjector($class, $const);
126
127
            return true;
128
        }
129
130
        // check interfaces
131
        foreach ($this->state->injectors as $target => $injector) {
132
            if (
133
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
134
                ||
135
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
136
            ) {
137
                $this->state->bindings[$class] = new Injectable($injector);
138
139
                return true;
140
            }
141
        }
142
143
        return false;
144
    }
145
146
    private function makeConfig(mixed $resolver, bool $singleton): Binding
147
    {
148
        return match (true) {
149
            $resolver instanceof Binding => $resolver,
150
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
151
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
152
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
153
            \is_string($resolver) => new Alias($resolver, $singleton),
154
            \is_scalar($resolver) => new Scalar($resolver),
155
            \is_object($resolver) => new Shared($resolver),
156
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
157
            default => throw new InvalidArgumentException('Unknown resolver type.'),
158
        };
159
    }
160
161
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
162
    {
163
        if (\is_callable($resolver)) {
164
            return new Factory($resolver, $singleton);
165
        }
166
167
        // Validate lazy invokable array
168
        if (!isset($resolver[0]) || !isset($resolver[1]) || !\is_string($resolver[1]) || $resolver[1] === '') {
169
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
170
        }
171
        if ((!\is_string($resolver[0]) && !\is_object($resolver[0])) || $resolver[0] === '') {
172
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
173
        }
174
175
        return new DeferredFactory($resolver, $singleton);
176
    }
177
178
    private function invalidBindingException(string $alias, mixed $resolver, Throwable $previous): Throwable
0 ignored issues
show
Unused Code introduced by
The parameter $resolver is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

178
    private function invalidBindingException(string $alias, /** @scrutinizer ignore-unused */ mixed $resolver, Throwable $previous): Throwable

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
    {
180
        return new ConfiguratorException(\sprintf(
181
            'Invalid binding for `%s`. %s',
182
            $alias,
183
            $previous->getMessage(),
184
        ), previous: $previous);
185
    }
186
}
187