Test Failed
Pull Request — master (#975)
by Maxim
20:11
created

StateBinder::setBinding()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
ccs 1
cts 1
cp 1
rs 10
cc 2
nc 2
nop 2
crap 2
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 1153
    public function __construct(
34
        protected readonly State $state,
35
    ) {
36 1153
    }
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 645
    public function bindSingleton(string $alias, mixed $resolver): void
61
    {
62
        try {
63 645
            $config = $this->makeConfig($resolver, true);
64
        } catch (\Throwable $e) {
65
            throw $this->invalidBindingException($alias, $e);
66
        }
67
68 645
        $this->setBinding($alias, $config);
69
    }
70
71 9
    public function hasInstance(string $alias): bool
72
    {
73 9
        $bindings = &$this->state->bindings;
74
75 9
        $flags = [];
76 9
        while ($binding = $bindings[$alias] ?? null and $binding::class === Alias::class) {
77
            //Checking alias tree
78 2
            if ($flags[$binding->alias] ?? false) {
79 1
                return $binding->alias === $alias ?: throw new Exception('Circular alias detected');
80
            }
81
82 2
            if (\array_key_exists($alias, $this->state->singletons)) {
83 2
                return true;
84
            }
85
86 8
            $flags[$binding->alias] = true;
87
            $alias = $binding->alias;
88
        }
89 628
90
        return \array_key_exists($alias, $this->state->singletons) or isset($bindings[$alias]);
91 628
    }
92
93
    public function removeBinding(string $alias): void
94 416
    {
95
        unset($this->state->bindings[$alias], $this->state->singletons[$alias]);
96 416
    }
97 416
98
    public function bindInjector(string $class, string $injector): void
99
    {
100
        $this->state->bindings[$class] = new Injectable($injector);
101
        $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 766
        }
110
        unset($this->state->bindings[$class]);
111
    }
112 766
113
    public function hasInjector(string $class): bool
114
    {
115
        try {
116
            $reflection = new \ReflectionClass($class);
117 766
        } catch (\ReflectionException $e) {
118 5
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
119
        }
120
121
        if (\array_key_exists($class, $this->state->injectors)) {
122 766
            return true;
123 766
        }
124
125 407
        if (
126 407
            $reflection->implementsInterface(InjectableInterface::class)
127
            && $reflection->hasConstant('INJECTOR')
128 407
        ) {
129
            $const = $reflection->getConstant('INJECTOR');
130
            $this->bindInjector($class, $const);
131
132 762
            return true;
133
        }
134 399
135
        // check interfaces
136 399
        foreach ($this->state->injectors as $target => $injector) {
137
            if (
138 7
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
139
                ||
140 7
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
141
            ) {
142
                $this->state->bindings[$class] = new Injectable($injector);
143
144 759
                return true;
145
            }
146
        }
147 924
148
        return false;
149 924
    }
150 924
151 924
    private function makeConfig(mixed $resolver, bool $singleton): Binding
152 924
    {
153 924
        return match (true) {
154 924
            $resolver instanceof Binding => $resolver,
155 924
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
156 924
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
157 924
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
158 924
            \is_string($resolver) => new Alias($resolver, $singleton),
159 924
            \is_scalar($resolver) => new Scalar($resolver),
160
            \is_object($resolver) => new Shared($resolver),
161
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
162 418
            default => throw new InvalidArgumentException('Unknown resolver type.'),
163
        };
164 418
    }
165
166
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
167
    {
168
        if (\is_callable($resolver)) {
169 418
            return new Factory($resolver, $singleton);
170
        }
171
172 418
        // Validate lazy invokable array
173
        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 1
180
        return new DeferredFactory($resolver, $singleton);
181 1
    }
182 1
183 1
    private function invalidBindingException(string $alias, Throwable $previous): Throwable
184 1
    {
185 1
        return new ConfiguratorException(\sprintf(
186
            'Invalid binding for `%s`. %s',
187
            $alias,
188 923
            $previous->getMessage(),
189
        ), previous: $previous);
190 923
    }
191 2
192
    private function setBinding(string $alias, Binding $config): void
193
    {
194 923
        if (isset($this->state->singletons[$alias])) {
195
            throw new SingletonOverloadException($alias);
196
        }
197
198
        $this->state->bindings[$alias] = $config;
199
    }
200
}
201