Passed
Push — master ( ad0a0c...f87c9b )
by butschster
09:41
created

StateBinder::makeConfigFromArray()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 13.2714

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 15
ccs 5
cts 8
cp 0.625
rs 8.0555
cc 9
nc 4
nop 2
crap 13.2714
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;
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
use Spiral\Core\Container\InjectableInterface;
20
use Spiral\Core\Exception\Binder\SingletonOverloadException;
21
use Spiral\Core\Exception\ConfiguratorException;
22
use Spiral\Core\Exception\Container\ContainerException;
23
use Spiral\Core\Internal\State;
24
use Throwable;
25
use WeakReference;
26
27
/**
28
 * @psalm-import-type TResolver from BinderInterface
29
 * @internal
30
 */
31
class StateBinder implements BinderInterface
32
{
33 1156
    public function __construct(
34
        protected readonly State $state,
35
    ) {
36 1156
    }
37
38
    /**
39
     * @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...
40
     */
41 865
    public function bind(string $alias, mixed $resolver): void
42
    {
43 865
        if ($resolver instanceof Inflector && (\interface_exists($alias) || \class_exists($alias))) {
44 5
            $this->state->inflectors[$alias][] = $resolver;
45 5
            return;
46
        }
47
48
        try {
49 861
            $config = $this->makeConfig($resolver, false);
50 1
        } catch (\Throwable $e) {
51 1
            throw $this->invalidBindingException($alias, $e);
52
        }
53
54 860
        $this->setBinding($alias, $config);
55
    }
56
57
    /**
58
     * @param TResolver|object $resolver
59
     */
60 648
    public function bindSingleton(string $alias, mixed $resolver): void
61
    {
62
        try {
63 648
            $config = $this->makeConfig($resolver, true);
64
        } catch (\Throwable $e) {
65
            throw $this->invalidBindingException($alias, $e);
66
        }
67
68 648
        $this->setBinding($alias, $config);
69
    }
70
71 12
    public function hasInstance(string $alias): bool
72
    {
73 12
        $bindings = &$this->state->bindings;
74
75 12
        $flags = [];
76 12
        while ($binding = $bindings[$alias] ?? null and $binding::class === Alias::class) {
77
            //Checking alias tree
78 4
            if ($flags[$binding->alias] ?? false) {
79
                return $binding->alias === $alias ?: throw new Exception('Circular alias detected');
80
            }
81
82 4
            if (\array_key_exists($alias, $this->state->singletons)) {
83 3
                return true;
84
            }
85
86 1
            $flags[$binding->alias] = true;
87 1
            $alias = $binding->alias;
88
        }
89
90 9
        return \array_key_exists($alias, $this->state->singletons) or isset($bindings[$alias]);
91
    }
92
93 628
    public function removeBinding(string $alias): void
94
    {
95 628
        unset($this->state->bindings[$alias], $this->state->singletons[$alias]);
96
    }
97
98 416
    public function bindInjector(string $class, string $injector): void
99
    {
100 416
        $this->state->bindings[$class] = new Injectable($injector);
101 416
        $this->state->injectors[$class] = $injector;
102
    }
103
104
    public function removeInjector(string $class): void
105
    {
106
        unset($this->state->injectors[$class]);
107
        if (!isset($this->state->bindings[$class]) || $this->state->bindings[$class]::class !== Injectable::class) {
108
            return;
109
        }
110
        unset($this->state->bindings[$class]);
111
    }
112
113 768
    public function hasInjector(string $class): bool
114
    {
115
        try {
116 768
            $reflection = new \ReflectionClass($class);
117
        } catch (\ReflectionException $e) {
118
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
119
        }
120
121 768
        if (\array_key_exists($class, $this->state->injectors)) {
122 5
            return true;
123
        }
124
125
        if (
126 768
            $reflection->implementsInterface(InjectableInterface::class)
127 768
            && $reflection->hasConstant('INJECTOR')
128
        ) {
129 407
            $const = $reflection->getConstant('INJECTOR');
130 407
            $this->bindInjector($class, $const);
131
132 407
            return true;
133
        }
134
135
        // check interfaces
136 764
        foreach ($this->state->injectors as $target => $injector) {
137
            if (
138 399
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
139
                ||
140 399
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
141
            ) {
142 7
                $this->state->bindings[$class] = new Injectable($injector);
143
144 7
                return true;
145
            }
146
        }
147
148 761
        return false;
149
    }
150
151 927
    private function makeConfig(mixed $resolver, bool $singleton): Binding
152
    {
153 927
        return match (true) {
154 927
            $resolver instanceof Binding => $resolver,
155 927
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
156 927
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
157 927
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
158 927
            \is_string($resolver) => new Alias($resolver, $singleton),
159 927
            \is_scalar($resolver) => new Scalar($resolver),
160 927
            \is_object($resolver) => new Shared($resolver),
161 927
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
162 927
            default => throw new InvalidArgumentException('Unknown resolver type.'),
163 927
        };
164
    }
165
166 418
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
167
    {
168 418
        if (\is_callable($resolver)) {
169
            return new Factory($resolver, $singleton);
170
        }
171
172
        // Validate lazy invokable array
173 418
        if (!isset($resolver[0]) || !isset($resolver[1]) || !\is_string($resolver[1]) || $resolver[1] === '') {
174
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
175
        }
176 418
        if ((!\is_string($resolver[0]) && !\is_object($resolver[0])) || $resolver[0] === '') {
177
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
178
        }
179
180 418
        return new DeferredFactory($resolver, $singleton);
181
    }
182
183 1
    private function invalidBindingException(string $alias, Throwable $previous): Throwable
184
    {
185 1
        return new ConfiguratorException(\sprintf(
186 1
            'Invalid binding for `%s`. %s',
187 1
            $alias,
188 1
            $previous->getMessage(),
189 1
        ), previous: $previous);
190
    }
191
192 926
    private function setBinding(string $alias, Binding $config): void
193
    {
194 926
        if (isset($this->state->singletons[$alias])) {
195 2
            throw new SingletonOverloadException($alias);
196
        }
197
198 926
        $this->state->bindings[$alias] = $config;
199
    }
200
}
201