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

DefinitionTrait::set()   C

Complexity

Conditions 16
Paths 73

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
cc 16
eloc 29
c 8
b 0
f 0
nc 73
nop 2
dl 0
loc 52
rs 5.5666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Helpers;
21
use PhpParser\Node\Expr\ArrowFunction;
22
use Rade\DI\{ContainerInterface, Definition, Definitions};
23
use Rade\DI\Exceptions\{FrozenServiceException, NotFoundServiceException};
24
25
/**
26
 * This trait adds definition's functionality to container.
27
 *
28
 * @property \Rade\DI\Resolver $resolver
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
trait DefinitionTrait
33
{
34
    use AliasTrait;
35
36
    /** @var array<string,Definitions\DefinitionInterface|object> service name => instance */
37
    protected array $definitions = [];
38
39
    /** @var array<string,mixed> A list of already public loaded services (this act as a local cache) */
40
    protected array $services = [];
41
42
    /** @var array<string,mixed> A list of already private loaded services (this act as a local cache) */
43
    protected array $privates = [];
44
45
    /**
46
     * {@inheritdoc}
47
     *
48
     * @return Definition or DefinitionInterface, mixed value which maybe object
49
     */
50
    public function definition(string $id)
51
    {
52
        return $this->definitions[$this->aliases[$id] ?? $id] ?? null;
53
    }
54
55
    /**
56
     * Gets all service definitions.
57
     *
58
     * @return array<string,Definitions\DefinitionInterface|object>
59
     */
60
    public function definitions(): array
61
    {
62
        return $this->definitions;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function initialized(string $id): bool
69
    {
70
        return \array_key_exists($this->aliases[$id] ?? $id, $this->services);
71
    }
72
73
    /**
74
     * Remove a registered definition.
75
     */
76
    public function removeDefinition(string $id): void
77
    {
78
        unset($this->definitions[$id], $this->services[$id]);
79
80
        foreach ($this->aliases as $alias => $aliased) {
81
            if ($id !== $aliased) {
82
                continue;
83
            }
84
85
            $this->removeAlias($alias);
86
        }
87
88
        if (isset($this->types)) {
89
            foreach ($this->types as &$serviceIds) {
90
                foreach ($serviceIds as $offset => $serviceId) {
91
                    if ($id !== $serviceId) {
92
                        continue;
93
                    }
94
95
                    unset($serviceIds[$offset]);
96
                }
97
            }
98
        }
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     *
104
     * @param Definitions\DefinitionInterface|object|null $definition
105
     *
106
     * @return Definition|Definitions\ValueDefinition or DefinitionInterface, mixed value which maybe object
107
     */
108
    public function set(string $id, object $definition = null): object
109
    {
110
        unset($this->aliases[$id]); // Incase new service definition exists in aliases.
111
112
        if (null !== ($this->services[$id] ?? $this->privates[$id] ?? null)) {
113
            throw new FrozenServiceException(\sprintf('The "%s" service is already initialized, and cannot be replaced.', $id));
114
        }
115
116
        if (ContainerInterface::SERVICE_CONTAINER === $id) {
117
            throw new FrozenServiceException(\sprintf('The "%s" service is reserved, and cannot be set or modified.', $id));
118
        }
119
120
        if (null === $definition) {
121
            ($definition = new Definition($id))->bindWith($id, $this);
122
        } elseif ($definition instanceof Definitions\Statement) {
123
            if ($definition->isClosureWrappable()) {
124
                if ($this->resolver->getBuilder()) {
125
                    $entity = new ArrowFunction(['expr' => $this->resolver->resolve($definition->getValue(), $definition->getArguments())]);
126
                }
127
                $definition = $entity ?? fn () => $this->resolver->resolve($definition->getValue(), $definition->getArguments());
128
            } else {
129
                $definition = new Definition($definition->getValue(), $definition->getArguments());
130
            }
131
        } elseif ($definition instanceof Definitions\Reference) {
132
            if (null === $previousDef = $this->definitions[(string) $definition] ?? null) {
133
                throw $this->createNotFound((string) $definition);
134
            }
135
            $definition = clone $previousDef;
136
137
            if ($definition instanceof Definitions\ShareableDefinitionInterface) {
138
                $definition->abstract(false);
139
            }
140
        }
141
142
        if ($definition instanceof Definitions\DefinitionInterface) {
143
            if ($definition instanceof Definitions\TypedDefinitionInterface) {
144
                $definition->isTyped() && $this->type($id, $definition->getTypes());
0 ignored issues
show
Bug introduced by
It seems like type() 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

144
                $definition->isTyped() && $this->/** @scrutinizer ignore-call */ type($id, $definition->getTypes());
Loading history...
145
            }
146
147
            if ($definition instanceof Definitions\DefinitionAwareInterface) {
148
                /** @var \Rade\DI\Definitions\Traits\DefinitionAwareTrait $definition */
149
                if ($definition->hasTags()) {
150
                    foreach ($definition->getTags() as $tag => $value) {
151
                        $this->tag($id, $tag, $value);
0 ignored issues
show
Bug introduced by
It seems like tag() 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

151
                        $this->/** @scrutinizer ignore-call */ 
152
                               tag($id, $tag, $value);
Loading history...
152
                    }
153
                }
154
155
                $definition->bindWith($id, $this);
156
            }
157
        }
158
159
        return $this->definitions[$id] = $definition;
160
    }
161
162
    /**
163
     * Sets multiple definitions at once into the container.
164
     *
165
     * @param array<int|string,mixed> $definitions indexed by their ids
166
     */
167
    public function multiple(array $definitions): void
168
    {
169
        foreach ($definitions as $id => $definition) {
170
            [$id, $definition] = \is_int($id) ? [$definition, null] : [$id, $definition];
171
            $this->set($id, $definition);
172
        }
173
    }
174
175
    /**
176
     * Replaces old service with a new one, but keeps a reference
177
     * of the old one as: service_id.inner.
178
     *
179
     * All decorated services under the tag: container.decorated_services
180
     *
181
     * @param Definitions\DefinitionInterface|object|null $definition
182
     *
183
     * @return Definition|Definitions\ValueDefinition or DefinitionInterface, mixed value which maybe object
184
     */
185
    public function decorate(string $id, object $definition = null, string $newId = null)
186
    {
187
        if (null === $innerDefinition = $this->definitions[$id] ?? null) {
188
            throw $this->createNotFound($id);
189
        }
190
191
        $this->removeDefinition($id);
192
        $this->set($id . '.inner', $innerDefinition)->tag('container.decorated_services');
193
194
        return $this->set($newId ?? $id, $definition);
195
    }
196
197
    /**
198
     * Throw a PSR-11 not found exception.
199
     */
200
    protected function createNotFound(string $id, \Throwable $e = null): NotFoundServiceException
201
    {
202
        if (null !== $suggest = Helpers::getSuggestion(\array_keys($this->definitions), $id)) {
203
            $suggest = " Did you mean: \"$suggest\"?";
204
        }
205
206
        return new NotFoundServiceException(\sprintf('The "%s" requested service is not defined in container.' . $suggest, $id), 0, $e);
207
    }
208
}
209