Test Failed
Pull Request — master (#37)
by Divine Niiquaye
11:41
created

DefinitionTrait::createNotFound()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

148
                $definition->isTyped() && $this->/** @scrutinizer ignore-call */ type($id, $definition->getTypes());
Loading history...
149
            }
150
151
            if ($definition instanceof Definitions\DefinitionAwareInterface) {
152
                /** @var \Rade\DI\Definitions\Traits\DefinitionAwareTrait $definition */
153
                if ($definition->hasTags()) {
154
                    foreach ($definition->getTags() as $tag => $value) {
155
                        $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

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