Passed
Pull Request — master (#941)
by Aleksei
13:25 queued 04:54
created

StateBinder   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Test Coverage

Coverage 84.42%

Importance

Changes 0
Metric Value
wmc 40
eloc 71
c 0
b 0
f 0
dl 0
loc 155
ccs 65
cts 77
cp 0.8442
rs 9.2

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A removeBinding() 0 3 1
A bindSingleton() 0 9 2
B makeConfigFromArray() 0 15 9
A bind() 0 14 5
A makeConfig() 0 12 1
A hasInstance() 0 16 6
A removeInjector() 0 7 3
B hasInjector() 0 36 10
A bindInjector() 0 4 1
A invalidBindingException() 0 7 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;
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Config\Factory 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...
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\ConfiguratorException;
21
use Spiral\Core\Exception\Container\ContainerException;
22
use Spiral\Core\Internal\State;
23
use Throwable;
24
use WeakReference;
25
26
/**
27
 * @psalm-import-type TResolver from BinderInterface
28
 * @internal
29
 */
30
class StateBinder implements BinderInterface
31
{
32 1122
    public function __construct(
33
        protected readonly State $state,
34
    ) {
35 1122
    }
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 833
    public function bind(string $alias, mixed $resolver): void
41
    {
42 833
        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 829
            $config = $this->makeConfig($resolver, false);
49 1
        } catch (\Throwable $e) {
50 1
            throw $this->invalidBindingException($alias, $e);
51
        }
52
53 828
        $this->state->bindings[$alias] = $config;
54
    }
55
56
    /**
57
     * @param TResolver|object $resolver
58
     */
59 605
    public function bindSingleton(string $alias, mixed $resolver): void
60
    {
61
        try {
62 605
            $config = $this->makeConfig($resolver, true);
63
        } catch (\Throwable $e) {
64
            throw $this->invalidBindingException($alias, $e);
65
        }
66
67 605
        $this->state->bindings[$alias] = $config;
68
    }
69
70 9
    public function hasInstance(string $alias): bool
71
    {
72 9
        $bindings = &$this->state->bindings;
73
74 9
        $flags = [];
75 9
        while ($binding = $bindings[$alias] ?? null and $binding::class === Alias::class) {
76
            //Checking alias tree
77 2
            if ($flags[$binding->alias] ?? false) {
78 1
                return $binding->alias === $alias ?: throw new Exception('Circular alias detected');
79
            }
80
81 2
            $flags[$binding->alias] = true;
82 2
            $alias = $binding->alias;
83
        }
84
85 8
        return isset($bindings[$alias]) && \is_object($bindings[$alias]);
86
    }
87
88 2
    public function removeBinding(string $alias): void
89
    {
90 2
        unset($this->state->bindings[$alias]);
91
    }
92
93 394
    public function bindInjector(string $class, string $injector): void
94
    {
95 394
        $this->state->bindings[$class] = new Injectable($injector);
96 394
        $this->state->injectors[$class] = $injector;
97
    }
98
99
    public function removeInjector(string $class): void
100
    {
101
        unset($this->state->injectors[$class]);
102
        if (!isset($this->state->bindings[$class]) || $this->state->bindings[$class]::class !== Injectable::class) {
103
            return;
104
        }
105
        unset($this->state->bindings[$class]);
106
    }
107
108 737
    public function hasInjector(string $class): bool
109
    {
110
        try {
111 737
            $reflection = new \ReflectionClass($class);
112
        } catch (\ReflectionException $e) {
113
            throw new ContainerException($e->getMessage(), $e->getCode(), $e);
114
        }
115
116 737
        if (\array_key_exists($class, $this->state->injectors)) {
117 6
            return true;
118
        }
119
120
        if (
121 737
            $reflection->implementsInterface(InjectableInterface::class)
122 737
            && $reflection->hasConstant('INJECTOR')
123
        ) {
124 385
            $const = $reflection->getConstant('INJECTOR');
125 385
            $this->bindInjector($class, $const);
126
127 385
            return true;
128
        }
129
130
        // check interfaces
131 733
        foreach ($this->state->injectors as $target => $injector) {
132
            if (
133 377
                (\class_exists($target, true) && $reflection->isSubclassOf($target))
134
                ||
135 377
                (\interface_exists($target, true) && $reflection->implementsInterface($target))
136
            ) {
137 7
                $this->state->bindings[$class] = new Injectable($injector);
138
139 7
                return true;
140
            }
141
        }
142
143 730
        return false;
144
    }
145
146 891
    private function makeConfig(mixed $resolver, bool $singleton): Binding
147
    {
148 891
        return match (true) {
149 891
            $resolver instanceof Binding => $resolver,
150 891
            $resolver instanceof \Closure => new Factory($resolver, $singleton),
151 891
            $resolver instanceof Autowire => new \Spiral\Core\Config\Autowire($resolver, $singleton),
152 891
            $resolver instanceof WeakReference => new \Spiral\Core\Config\WeakReference($resolver),
153 891
            \is_string($resolver) => new Alias($resolver, $singleton),
154 891
            \is_scalar($resolver) => new Scalar($resolver),
155 891
            \is_object($resolver) => new Shared($resolver),
156 891
            \is_array($resolver) => $this->makeConfigFromArray($resolver, $singleton),
157 891
            default => throw new InvalidArgumentException('Unknown resolver type.'),
158 891
        };
159
    }
160
161 399
    private function makeConfigFromArray(array $resolver, bool $singleton): Binding
162
    {
163 399
        if (\is_callable($resolver)) {
164
            return new Factory($resolver, $singleton);
165
        }
166
167
        // Validate lazy invokable array
168 399
        if (!isset($resolver[0]) || !isset($resolver[1]) || !\is_string($resolver[1]) || $resolver[1] === '') {
169
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
170
        }
171 399
        if ((!\is_string($resolver[0]) && !\is_object($resolver[0])) || $resolver[0] === '') {
172
            throw new InvalidArgumentException('Incompatible array declaration for resolver.');
173
        }
174
175 399
        return new DeferredFactory($resolver, $singleton);
176
    }
177
178 1
    private function invalidBindingException(string $alias, Throwable $previous): Throwable
179
    {
180 1
        return new ConfiguratorException(\sprintf(
181 1
            'Invalid binding for `%s`. %s',
182 1
            $alias,
183 1
            $previous->getMessage(),
184 1
        ), previous: $previous);
185
    }
186
}
187