Test Failed
Pull Request — master (#37)
by Divine Niiquaye
03:19
created

TypesTrait::autowire()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 2
b 0
f 0
nc 4
nop 2
dl 0
loc 17
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI\Traits;
19
20
use Nette\Utils\Validators;
21
use Rade\DI\{Definition, Definitions, Extensions, Services};
22
use Rade\DI\Definitions\{DefinitionInterface, TypedDefinitionInterface};
23
use Rade\DI\Exceptions\{ContainerResolutionException, NotFoundServiceException};
24
use Rade\DI\Resolver;
25
use Symfony\Contracts\Service\{ResetInterface, ServiceSubscriberInterface};
26
27
/**
28
 * This trait adds autowiring functionality to container.
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
trait TypesTrait
33
{
34
    protected Resolver $resolver;
35
36
    /** @var array<string,string[]> type => services */
37
    protected array $types = [];
38
39
    /** @var array<string,bool> of classes excluded from autowiring */
40
    protected array $excluded = [
41
        \ArrayAccess::class => true,
42
        \Countable::class => true,
43
        \IteratorAggregate::class => true,
44
        \SplDoublyLinkedList::class => true,
45
        \stdClass::class => true,
46
        \SplStack::class => true,
47
        \Stringable::class => true,
48
        \Reflector::class => true,
49
        \Iterator::class => true,
50
        \Traversable::class => true,
51
        \RecursiveIterator::class => true,
52
        \Serializable::class => true,
53
        \JsonSerializable::class => true,
54
        ResetInterface::class => true,
55
        ServiceSubscriberInterface::class => true,
56
        Extensions\BootExtensionInterface::class => true,
57
        Extensions\DebugExtensionInterface::class => true,
58
        Extensions\ExtensionInterface::class => true,
59
        Services\DependenciesInterface::class => true,
0 ignored issues
show
Bug introduced by
The type Rade\DI\Services\DependenciesInterface 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...
60
        Services\AliasedInterface::class => true,
0 ignored issues
show
Bug introduced by
The type Rade\DI\Services\AliasedInterface 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...
61
        Services\ServiceProviderInterface::class => true,
62
        Services\ServiceLocator::class => true,
63
        Definitions\DefinitionInterface::class => true,
64
        Definitions\TypedDefinitionInterface::class => true,
65
        Definitions\ShareableDefinitionInterface::class => true,
66
        Definitions\DepreciableDefinitionInterface::class => true,
67
        Definitions\DefinitionAwareInterface::class => true,
68
        Definitions\ValueDefinition::class => true,
69
        Definitions\Reference::class => true,
70
        Definitions\Statement::class => true,
71
        Definition::class => true,
72
    ];
73
74
    /**
75
     * Remove an aliased type set to service(s).
76
     */
77
    final public function removeType(string $type): void
78
    {
79
        unset($this->types[$type]);
80
    }
81
82
    /**
83
     * Add a class or interface that should be excluded from autowiring.
84
     *
85
     * @param string ...$types
86
     */
87
    public function excludeType(string ...$types): void
88
    {
89
        foreach ($types as $type) {
90
            $this->excluded[$type] = true;
91
        }
92
    }
93
94
    /**
95
     * Add a aliased type of classes or interfaces for a service definition.
96
     *
97
     * @param string          $id    The registered service id
98
     * @param string|string[] $types The types associated with service definition
99
     */
100
    public function type(string $id, $types): void
101
    {
102
        foreach ((array) $types as $typed) {
103
            if (!Validators::isType($typed)) {
104
                continue;
105
            }
106
107
            if ($this->excluded[$typed] ?? \in_array($id, $this->types[$typed] ?? [], true)) {
108
                continue;
109
            }
110
111
            $this->types[$typed][] = $id;
112
        }
113
    }
114
115
    /**
116
     * Set types for multiple services.
117
     *
118
     * @see type method
119
     *
120
     * @param string[] $types The types associated with service definition
121
     */
122
    public function types(array $types): void
123
    {
124
        foreach ($types as $id => $wiring) {
125
            if (\is_int($id)) {
126
                throw new ContainerResolutionException('Service identifier is not defined, integer found.');
127
            }
128
129
            $this->type($id, (array) $wiring);
130
        }
131
    }
132
133
    /**
134
     * If class or interface is a autowired typed value.
135
     *
136
     * @param string $id of class or interface
137
     *
138
     * @return bool|string[] If $ids is true, returns found ids else bool
139
     */
140
    public function typed(string $id, bool $ids = false)
141
    {
142
        return $ids ? $this->types[$id] ?? [] : \array_key_exists($id, $this->types);
143
    }
144
145
    /**
146
     * Alias of container's set method with default autowiring.
147
     *
148
     * @param DefinitionInterface|object|null $definition
149
     *
150
     * @return Definition or DefinitionInterface, mixed value which maybe object
151
     */
152
    public function autowire(string $id, object $definition = null): object
153
    {
154
        $definition = $this->set($id, $definition);
0 ignored issues
show
Bug introduced by
It seems like set() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

154
        /** @scrutinizer ignore-call */ 
155
        $definition = $this->set($id, $definition);
Loading history...
155
156
        if ($this->typed($id)) {
157
            return $definition;
158
        }
159
160
        if ($definition instanceof TypedDefinitionInterface) {
161
            return $definition->autowire();
162
        }
163
164
        if (!$definition instanceof DefinitionInterface) {
165
            $this->type($id, Resolver::autowireService($definition, false, $this));
166
        }
167
168
        return $definition;
169
    }
170
171
    /**
172
     * If id is a registered service id, return bool else if types is set on id,
173
     * resolve value and return it.
174
     *
175
     * @param string $id A class or an interface name
176
     *
177
     * @throws ContainerResolutionException|NotFoundServiceException
178
     *
179
     * @return mixed
180
     */
181
    public function autowired(string $id, bool $single = false)
182
    {
183
        if (empty($autowired = $this->types[$id] ?? [])) {
184
            throw new NotFoundServiceException(\sprintf('Service of type "%s" not found. Check class name because it cannot be found.', $id));
185
        }
186
187
        if (1 === \count($autowired)) {
188
            return $this->services[$autowired[0]] ?? $this->get($autowired[0]);
0 ignored issues
show
Bug introduced by
It seems like get() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

188
            return $this->services[$autowired[0]] ?? $this->/** @scrutinizer ignore-call */ get($autowired[0]);
Loading history...
189
        }
190
191
        if ($single) {
192
            \natsort($autowired);
193
            $autowired = count($autowired) <= 3 ? \implode(', ', $autowired) : $autowired[0] . ', ...' . \end($autowired);
194
195
            throw new ContainerResolutionException(\sprintf('Multiple services of type %s found: %s.', $id, $autowired));
196
        }
197
198
        return \array_map(fn (string $id) => $this->services[$id] ?? $this->get($id), $autowired);
199
    }
200
201
    /**
202
     * The resolver associated with the container.
203
     */
204
    public function getResolver(): Resolver
205
    {
206
        return $this->resolver;
207
    }
208
}
209