Passed
Push — master ( 5baf23...e2b43f )
by butschster
14:50 queued 06:10
created

StateBinder   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Test Coverage

Coverage 85.19%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 42
eloc 74
c 1
b 0
f 1
dl 0
loc 164
ccs 69
cts 81
cp 0.8519
rs 9.0399

12 Methods

Rating   Name   Duplication   Size   Complexity  
A bind() 0 14 5
A __construct() 0 3 1
A bindSingleton() 0 9 2
B makeConfigFromArray() 0 15 9
A hasInstance() 0 16 6
A makeConfig() 0 12 1
A removeInjector() 0 7 3
A removeBinding() 0 3 1
B hasInjector() 0 36 10
A setBinding() 0 7 2
A invalidBindingException() 0 7 1
A bindInjector() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like StateBinder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StateBinder, and based on these observations, apply Extract Interface, too.

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 1138
    public function __construct(
34
        protected readonly State $state,
35
    ) {
36 1138
    }
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 851
    public function bind(string $alias, mixed $resolver): void
42
    {
43 851
        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 847
            $config = $this->makeConfig($resolver, false);
50 1
        } catch (\Throwable $e) {
51 1
            throw $this->invalidBindingException($alias, $e);
52
        }
53
54 846
        $this->setBinding($alias, $config);
55
    }
56
57
    /**
58
     * @param TResolver|object $resolver
59
     */
60 623
    public function bindSingleton(string $alias, mixed $resolver): void
61
    {
62
        try {
63 623
            $config = $this->makeConfig($resolver, true);
64
        } catch (\Throwable $e) {
65
            throw $this->invalidBindingException($alias, $e);
66
        }
67
68 623
        $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
            $flags[$binding->alias] = true;
83 2
            $alias = $binding->alias;
84
        }
85
86 8
        return isset($bindings[$alias]) && \is_object($bindings[$alias]);
87
    }
88
89 3
    public function removeBinding(string $alias): void
90
    {
91 3
        unset($this->state->bindings[$alias], $this->state->singletons[$alias]);
92
    }
93
94 404
    public function bindInjector(string $class, string $injector): void
95
    {
96 404
        $this->state->bindings[$class] = new Injectable($injector);
97 404
        $this->state->injectors[$class] = $injector;
98
    }
99
100
    public function removeInjector(string $class): void
101
    {
102
        unset($this->state->injectors[$class]);
103
        if (!isset($this->state->bindings[$class]) || $this->state->bindings[$class]::class !== Injectable::class) {
104
            return;
105
        }
106
        unset($this->state->bindings[$class]);
107
    }
108
109 751
    public function hasInjector(string $class): bool
110
    {
111
        try {
112 751
            $reflection = new \ReflectionClass($class);
113
        } catch (\ReflectionException $e) {
114
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
115
        }
116
117 751
        if (\array_key_exists($class, $this->state->injectors)) {
118 6
            return true;
119
        }
120
121
        if (
122 751
            $reflection->implementsInterface(InjectableInterface::class)
123 751
            && $reflection->hasConstant('INJECTOR')
124
        ) {
125 395
            $const = $reflection->getConstant('INJECTOR');
126 395
            $this->bindInjector($class, $const);
127
128 395
            return true;
129
        }
130
131
        // check interfaces
132 747
        foreach ($this->state->injectors as $target => $injector) {
133
            if (
134 387
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
135
                ||
136 387
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
137
            ) {
138 7
                $this->state->bindings[$class] = new Injectable($injector);
139
140 7
                return true;
141
            }
142
        }
143
144 744
        return false;
145
    }
146
147 910
    private function makeConfig(mixed $resolver, bool $singleton): Binding
148
    {
149 910
        return match (true) {
150 910
            $resolver instanceof Binding => $resolver,
151 910
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
152 910
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
153 910
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
154 910
            \is_string($resolver) => new Alias($resolver, $singleton),
155 910
            \is_scalar($resolver) => new Scalar($resolver),
156 910
            \is_object($resolver) => new Shared($resolver),
157 910
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
158 910
            default => throw new InvalidArgumentException('Unknown resolver type.'),
159 910
        };
160
    }
161
162 408
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
163
    {
164 408
        if (\is_callable($resolver)) {
165
            return new Factory($resolver, $singleton);
166
        }
167
168
        // Validate lazy invokable array
169 408
        if (!isset($resolver[0]) || !isset($resolver[1]) || !\is_string($resolver[1]) || $resolver[1] === '') {
170
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
171
        }
172 408
        if ((!\is_string($resolver[0]) && !\is_object($resolver[0])) || $resolver[0] === '') {
173
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
174
        }
175
176 408
        return new DeferredFactory($resolver, $singleton);
177
    }
178
179 1
    private function invalidBindingException(string $alias, Throwable $previous): Throwable
180
    {
181 1
        return new ConfiguratorException(\sprintf(
182 1
            'Invalid binding for `%s`. %s',
183 1
            $alias,
184 1
            $previous->getMessage(),
185 1
        ), previous: $previous);
186
    }
187
188 909
    private function setBinding(string $alias, Binding $config): void
189
    {
190 909
        if (isset($this->state->singletons[$alias])) {
191 2
            throw new SingletonOverloadException($alias);
192
        }
193
194 909
        $this->state->bindings[$alias] = $config;
195
    }
196
}
197