Passed
Push — master ( 4be7e4...e88381 )
by butschster
07:01
created

Container::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
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Core;
6
7
use Psr\Container\ContainerInterface;
8
use ReflectionFunctionAbstract as ContextFunction;
9
use Spiral\Core\Container\Autowire;
10
use Spiral\Core\Container\InjectableInterface;
11
use Spiral\Core\Container\SingletonInterface;
12
use Spiral\Core\Exception\Container\ContainerException;
13
use Spiral\Core\Exception\LogicException;
14
use Spiral\Core\Internal\DestructorTrait;
15
16
/**
17
 * Auto-wiring container: declarative singletons, contextual injections, parent container
18
 * delegation and ability to lazy wire.
19
 *
20
 * Container does not support setter injections, private properties and etc. Normally it will work
21
 * with classes only to be as much invisible as possible. Attention, this is hungry implementation
22
 * of container, meaning it WILL try to resolve dependency unless you specified custom lazy
23
 * factory.
24
 *
25
 * You can use injectors to delegate class resolution to external container.
26
 *
27
 * @see \Spiral\Core\Container::registerInstance() to add your own behaviours.
28
 *
29
 * @see InjectableInterface
30
 * @see SingletonInterface
31
 *
32
 * @psalm-import-type TResolver from BinderInterface
33
 * @psalm-import-type TInvokable from InvokerInterface
34
 * @psalm-suppress PropertyNotSetInConstructor
35
 */
36
final class Container implements
37
    ContainerInterface,
38
    BinderInterface,
39
    FactoryInterface,
40
    ResolverInterface,
41
    InvokerInterface,
42
    ScopeInterface
43
{
44
    use DestructorTrait;
45
46
    private Internal\State $state;
47
    private ResolverInterface|Internal\Resolver $resolver;
48
    private FactoryInterface|Internal\Factory $factory;
49
    private ContainerInterface|Internal\Container $container;
50
    private BinderInterface|Internal\Binder $binder;
51
    private InvokerInterface|Internal\Invoker $invoker;
0 ignored issues
show
Bug introduced by
The type Spiral\Core\Internal\Invoker 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...
52
53
    /**
54
     * Container constructor.
55
     */
56 866
    public function __construct(Config $config = new Config())
57
    {
58 866
        $constructor = new Internal\Registry($config, [
59 866
            'state' => new Internal\State(),
60
        ]);
61 866
        foreach ($config as $property => $class) {
62 866
            if (\property_exists($this, $property)) {
63 866
                $this->$property = $constructor->get($property, $class);
64
            }
65
        }
66
67
        /** @psalm-suppress PossiblyNullPropertyAssignment */
68 866
        $this->state->bindings = [
69 866
            self::class               => \WeakReference::create($this),
70
            ContainerInterface::class => self::class,
71
            BinderInterface::class    => self::class,
72
            FactoryInterface::class   => self::class,
73
            ScopeInterface::class     => self::class,
74
            ResolverInterface::class  => self::class,
75
            InvokerInterface::class   => self::class,
76
        ];
77
    }
78
79 486
    public function __destruct()
80
    {
81 486
        $this->destruct();
82
    }
83
84
    /**
85
     * Container can not be cloned.
86
     */
87 1
    public function __clone()
88
    {
89 1
        throw new LogicException('Container is not clonable.');
90
    }
91
92 372
    public function resolveArguments(
93
        ContextFunction $reflection,
94
        array $parameters = [],
95
        bool $validate = true,
96
    ): array {
97 372
        return $this->resolver->resolveArguments($reflection, $parameters, $validate);
98
    }
99
100
    public function validateArguments(ContextFunction $reflection, array $arguments = []): void
101
    {
102
        $this->resolver->validateArguments($reflection, $arguments);
103
    }
104
105
    /**
106
     * @param string|null $context Related to parameter caused injection if any.
107
     */
108 440
    public function make(string $alias, array $parameters = [], string $context = null): mixed
109
    {
110 440
        return $this->factory->make($alias, $parameters, $context);
0 ignored issues
show
Unused Code introduced by
The call to Spiral\Core\FactoryInterface::make() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

110
        return $this->factory->/** @scrutinizer ignore-call */ make($alias, $parameters, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
111
    }
112
113
    /**
114
     * Context parameter will be passed to class injectors, which makes possible to use this method
115
     * as:
116
     *
117
     * $this->container->get(DatabaseInterface::class, 'default');
118
     *
119
     * Attention, context ignored when outer container has instance by alias.
120
     *
121
     * @template T
122
     *
123
     * @param class-string<T>|string|Autowire $id
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T>|string|Autowire at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>|string|Autowire.
Loading history...
124
     * @param string|null $context Call context.
125
     *
126
     * @return T
127
     * @psalm-return ($id is class-string ? T : mixed)
128
     *
129
     * @throws ContainerException
130
     * @throws \Throwable
131
     *
132
     * @psalm-suppress PossiblyInvalidArgument, PossiblyInvalidCast
133
     */
134 674
    public function get(string|Autowire $id, string $context = null): mixed
135
    {
136 674
        return $this->container->get($id, $context);
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type Spiral\Core\Container\Autowire; however, parameter $id of Psr\Container\ContainerInterface::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
        return $this->container->get(/** @scrutinizer ignore-type */ $id, $context);
Loading history...
Unused Code introduced by
The call to Psr\Container\ContainerInterface::get() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

136
        return $this->container->/** @scrutinizer ignore-call */ get($id, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
137
    }
138
139 405
    public function has(string $id): bool
140
    {
141 405
        return $this->container->has($id);
142
    }
143
144 470
    public function runScope(array $bindings, callable $scope): mixed
145
    {
146 470
        $binds = &$this->state->bindings;
147 470
        $cleanup = $previous = [];
148 470
        foreach ($bindings as $alias => $resolver) {
149 469
            if (isset($binds[$alias])) {
150 439
                $previous[$alias] = $binds[$alias];
151
            } else {
152 236
                $cleanup[] = $alias;
153
            }
154
155 469
            $this->binder->bind($alias, $resolver);
156
        }
157
158
        try {
159 470
            return ContainerScope::getContainer() !== $this
160 469
                ? ContainerScope::runScope($this, $scope)
161 456
                : $scope($this);
162
        } finally {
163 470
            foreach ($previous as $alias => $resolver) {
164 439
                $binds[$alias] = $resolver;
165
            }
166
167 470
            foreach ($cleanup as $alias) {
168 236
                unset($binds[$alias]);
169
            }
170
        }
171
    }
172
173
    /**
174
     * Bind value resolver to container alias. Resolver can be class name (will be constructed
175
     * for each method call), function array or Closure (executed every call). Only object resolvers
176
     * supported by this method.
177
     */
178 673
    public function bind(string $alias, string|array|callable|object $resolver): void
179
    {
180 673
        $this->binder->bind($alias, $resolver);
181
    }
182
183
    /**
184
     * Bind value resolver to container alias to be executed as cached. Resolver can be class name
185
     * (will be constructed only once), function array or Closure (executed only once call).
186
     *
187
     * @psalm-param TResolver $resolver
188
     */
189 508
    public function bindSingleton(string $alias, string|array|callable|object $resolver): void
190
    {
191 508
        $this->binder->bindSingleton($alias, $resolver);
192
    }
193
194
    /**
195
     * Check if alias points to constructed instance (singleton).
196
     */
197 5
    public function hasInstance(string $alias): bool
198
    {
199 5
        return $this->binder->hasInstance($alias);
200
    }
201
202 2
    public function removeBinding(string $alias): void
203
    {
204 2
        $this->binder->removeBinding($alias);
205
    }
206
207
    /**
208
     * @psalm-param TInvokable $target
209
     */
210 473
    public function invoke(mixed $target, array $parameters = []): mixed
211
    {
212 473
        return $this->invoker->invoke($target, $parameters);
213
    }
214
215
    /**
216
     * Bind class or class interface to the injector source (InjectorInterface).
217
     */
218 305
    public function bindInjector(string $class, string $injector): void
219
    {
220 305
        $this->binder->bindInjector($class, $injector);
221
    }
222
223
    public function removeInjector(string $class): void
224
    {
225
        $this->binder->removeInjector($class);
226
    }
227
228 8
    public function hasInjector(string $class): bool
229
    {
230 8
        return $this->binder->hasInjector($class);
231
    }
232
}
233