Passed
Pull Request — master (#955)
by Maxim
08:40
created

StateBinder::hasInstance()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 8
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.2222
c 1
b 0
f 1
cc 6
nc 5
nop 1
crap 6
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 1136
    public function __construct(
34
        protected readonly State $state,
35
    ) {
36 1136
    }
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 849
    public function bind(string $alias, mixed $resolver): void
42
    {
43 849
        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 845
            $config = $this->makeConfig($resolver, false);
50 1
        } catch (\Throwable $e) {
51 1
            throw $this->invalidBindingException($alias, $e);
52
        }
53
54 844
        $this->state->bindings[$alias] = $config;
55
    }
56
57
    /**
58
     * @param TResolver|object $resolver
59
     */
60 621
    public function bindSingleton(string $alias, mixed $resolver): void
61
    {
62 621
        if (isset($this->state->singletons[$alias])) {
63 1
            throw new SingletonOverloadException(
64 1
                \sprintf('Can\'t overload the singleton `%s` because it\'s already used.', $alias)
65 1
            );
66
        }
67
68
        try {
69 621
            $config = $this->makeConfig($resolver, true);
70
        } catch (\Throwable $e) {
71
            throw $this->invalidBindingException($alias, $e);
72
        }
73
74 621
        $this->state->bindings[$alias] = $config;
75
    }
76
77 9
    public function hasInstance(string $alias): bool
78
    {
79 9
        $bindings = &$this->state->bindings;
80
81 9
        $flags = [];
82 9
        while ($binding = $bindings[$alias] ?? null and $binding::class === Alias::class) {
83
            //Checking alias tree
84 2
            if ($flags[$binding->alias] ?? false) {
85 1
                return $binding->alias === $alias ?: throw new Exception('Circular alias detected');
86
            }
87
88 2
            $flags[$binding->alias] = true;
89 2
            $alias = $binding->alias;
90
        }
91
92 8
        return isset($bindings[$alias]) && \is_object($bindings[$alias]);
93
    }
94
95 3
    public function removeBinding(string $alias): void
96
    {
97 3
        unset($this->state->bindings[$alias], $this->state->singletons[$alias]);
98
    }
99
100 403
    public function bindInjector(string $class, string $injector): void
101
    {
102 403
        $this->state->bindings[$class] = new Injectable($injector);
103 403
        $this->state->injectors[$class] = $injector;
104
    }
105
106
    public function removeInjector(string $class): void
107
    {
108
        unset($this->state->injectors[$class]);
109
        if (!isset($this->state->bindings[$class]) || $this->state->bindings[$class]::class !== Injectable::class) {
110
            return;
111
        }
112
        unset($this->state->bindings[$class]);
113
    }
114
115 750
    public function hasInjector(string $class): bool
116
    {
117
        try {
118 750
            $reflection = new \ReflectionClass($class);
119
        } catch (\ReflectionException $e) {
120
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
121
        }
122
123 750
        if (\array_key_exists($class, $this->state->injectors)) {
124 6
            return true;
125
        }
126
127
        if (
128 750
            $reflection->implementsInterface(InjectableInterface::class)
129 750
            && $reflection->hasConstant('INJECTOR')
130
        ) {
131 394
            $const = $reflection->getConstant('INJECTOR');
132 394
            $this->bindInjector($class, $const);
133
134 394
            return true;
135
        }
136
137
        // check interfaces
138 746
        foreach ($this->state->injectors as $target => $injector) {
139
            if (
140 386
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
141
                ||
142 386
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
143
            ) {
144 7
                $this->state->bindings[$class] = new Injectable($injector);
145
146 7
                return true;
147
            }
148
        }
149
150 743
        return false;
151
    }
152
153 908
    private function makeConfig(mixed $resolver, bool $singleton): Binding
154
    {
155 908
        return match (true) {
156 908
            $resolver instanceof Binding => $resolver,
157 908
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
158 908
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
159 908
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
160 908
            \is_string($resolver) => new Alias($resolver, $singleton),
161 908
            \is_scalar($resolver) => new Scalar($resolver),
162 908
            \is_object($resolver) => new Shared($resolver),
163 908
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
164 908
            default => throw new InvalidArgumentException('Unknown resolver type.'),
165 908
        };
166
    }
167
168 407
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
169
    {
170 407
        if (\is_callable($resolver)) {
171
            return new Factory($resolver, $singleton);
172
        }
173
174
        // Validate lazy invokable array
175 407
        if (!isset($resolver[0]) || !isset($resolver[1]) || !\is_string($resolver[1]) || $resolver[1] === '') {
176
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
177
        }
178 407
        if ((!\is_string($resolver[0]) && !\is_object($resolver[0])) || $resolver[0] === '') {
179
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
180
        }
181
182 407
        return new DeferredFactory($resolver, $singleton);
183
    }
184
185 1
    private function invalidBindingException(string $alias, Throwable $previous): Throwable
186
    {
187 1
        return new ConfiguratorException(\sprintf(
188 1
            'Invalid binding for `%s`. %s',
189 1
            $alias,
190 1
            $previous->getMessage(),
191 1
        ), previous: $previous);
192
    }
193
}
194