Passed
Pull Request — master (#1199)
by butschster
22:19 queued 10:07
created

StateBinder::bindInjector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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