Test Failed
Pull Request — master (#37)
by Divine Niiquaye
13:18
created

TypesTrait::removeType()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 2
b 0
f 0
nc 4
nop 2
dl 0
loc 10
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, string $serviceId = null): void
78
    {
79
        if (null !== $serviceId) {
80
            foreach ($this->types[$type] ?? [] as $offset => $typed) {
81
                if ($serviceId === $typed) {
82
                    unset($this->types[$type][$offset]);
83
                }
84
            }
85
        } else {
86
            unset($this->types[$type]);
87
        }
88
    }
89
90
    /**
91
     * Add a class or interface that should be excluded from autowiring.
92
     *
93
     * @param string ...$types
94
     */
95
    public function excludeType(string ...$types): void
96
    {
97
        foreach ($types as $type) {
98
            $this->excluded[$type] = true;
99
        }
100
    }
101
102
    /**
103
     * Add a aliased type of classes or interfaces for a service definition.
104
     *
105
     * @param string          $id    The registered service id
106
     * @param string|string[] $types The types associated with service definition
107
     */
108
    public function type(string $id, $types): void
109
    {
110
        foreach ((array) $types as $typed) {
111
            if (!Validators::isType($typed)) {
112
                continue;
113
            }
114
115
            if ($this->excluded[$typed] ?? \in_array($id, $this->types[$typed] ?? [], true)) {
116
                continue;
117
            }
118
119
            $this->types[$typed][] = $id;
120
        }
121
    }
122
123
    /**
124
     * Set types for multiple services.
125
     *
126
     * @see type method
127
     *
128
     * @param string[] $types The types associated with service definition
129
     */
130
    public function types(array $types): void
131
    {
132
        foreach ($types as $id => $wiring) {
133
            if (\is_int($id)) {
134
                throw new ContainerResolutionException('Service identifier is not defined, integer found.');
135
            }
136
137
            $this->type($id, (array) $wiring);
138
        }
139
    }
140
141
    /**
142
     * If class or interface is a autowired typed value.
143
     *
144
     * @param string $id of class or interface
145
     *
146
     * @return bool|string[] If $ids is true, returns found ids else bool
147
     */
148
    public function typed(string $id, bool $ids = false)
149
    {
150
        return $ids ? $this->types[$id] ?? [] : \array_key_exists($id, $this->types);
151
    }
152
153
    /**
154
     * Alias of container's set method with default autowiring.
155
     *
156
     * @param DefinitionInterface|object|null $definition
157
     *
158
     * @return Definition or DefinitionInterface, mixed value which maybe object
159
     */
160
    public function autowire(string $id, object $definition = null): object
161
    {
162
        $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

162
        /** @scrutinizer ignore-call */ 
163
        $definition = $this->set($id, $definition);
Loading history...
163
164
        if ($definition instanceof TypedDefinitionInterface && !$definition->isTyped()) {
165
            return $definition->autowire();
166
        }
167
168
        if (!$definition instanceof DefinitionInterface) {
169
            $this->type($id, Resolver::autowireService($definition, false, $this));
170
        }
171
172
        return $definition;
173
    }
174
175
    /**
176
     * If id is a registered service id, return bool else if types is set on id,
177
     * resolve value and return it.
178
     *
179
     * @param string $id A class or an interface name
180
     *
181
     * @throws ContainerResolutionException|NotFoundServiceException
182
     *
183
     * @return mixed
184
     */
185
    public function autowired(string $id, bool $single = false)
186
    {
187
        if (empty($autowired = $this->types[$id] ?? [])) {
188
            throw new NotFoundServiceException(\sprintf('Service of type "%s" not found. Check class name because it cannot be found.', $id));
189
        }
190
191
        if ($single) {
192
            if (1 === $c = \count($autowired)) {
193
                return $this->services[$id = \current($autowired)] ?? $this->get($id);
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

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