StateBinder::hasInstance()   B
last analyzed

Complexity

Conditions 7
Paths 6

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.0368

Importance

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