Container::set()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 6 Features 0
Metric Value
c 8
b 6
f 0
dl 0
loc 16
rs 9.4285
cc 3
eloc 10
nc 4
nop 2
1
<?php
2
/**
3
 * This file is part of the Stack package.
4
 *
5
 * (c) Andrzej Kostrzewa <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Stack\DI;
12
13
use Interop\Container\ContainerInterface;
14
use Interop\Container\Exception\ContainerException;
15
use Interop\Container\Exception\NotFoundException;
16
use Stack\DI\Definition\AliasDefinition;
17
use Stack\DI\Definition\Source\DefinitionSourceInterface;
18
19
/**
20
 * Dependency Injection Container.
21
 *
22
 * @author Andrzej Kostrzewa <[email protected]>
23
 */
24
class Container implements ContainerInterface
25
{
26
    /**
27
     * @var callable[]
28
     */
29
    private $serviceFactory = [];
30
31
    /**
32
     * @var array
33
     */
34
    private $services = [];
35
36
    /**
37
     * @var ContainerInterface
38
     */
39
    private $delegateContainer;
40
41
    /**
42
     * @var DefinitionSourceInterface
43
     */
44
    private $definitionSource;
45
46
    /**
47
     * @var array
48
     */
49
    private $aliasDefinitions = [];
50
51
    /**
52
     * @var bool
53
     */
54
    private $useServiceFactory = false;
55
56
    /**
57
     * Container constructor.
58
     *
59
     * @param DefinitionSourceInterface $definitionSource
60
     * @param null|ContainerInterface   $delegateContainer
61
     */
62
    public function __construct(
63
        DefinitionSourceInterface $definitionSource,
64
        ContainerInterface $delegateContainer = null
65
    ) {
66
        $this->definitionSource  = $definitionSource;
67
        $this->delegateContainer = $delegateContainer;
68
69
        $this->services['container'] = $this;
70
    }
71
72
    /**
73
     * Finds an entry of the container by its identifier and returns it.
74
     *
75
     * @param string $name Identifier of the entry to look for.
76
     *
77
     * @throws NotFoundException  No entry was found for this identifier.
78
     * @throws ContainerException Error while retrieving the entry.
79
     *
80
     * @return mixed Entry.
81
     */
82
    public function get($name)
83
    {
84
        $service = $name;
85
        $name    = strtolower($name);
86
87
        if (!$this->hasAlias($name) && !$this->has($name)) {
88
            $this->setAlias($name);
89
        }
90
91
        if ($this->hasAlias($name)) {
92
            $name = $this->getAlias($name);
93
        }
94
95
        if ($this->has($name)) {
96
            $this->useServiceFactory = false;
97
98
            return $this->services[$name];
99
        }
100
101
        if ($this->useServiceFactory) {
102
            if ($this->hasServiceInFactory($name)) {
103
                $service = $this->getServiceFromFactory($name);
104
            }
105
        }
106
107
        if ($this->definitionSource !== null) {
108
            $service = $this->definitionSource->get($service);
109
        }
110
111
        $this->services[$name] = $service;
112
113
        return $service;
114
    }
115
116
    /**
117
     * Returns true if the container can return an entry for the given identifier.
118
     * Returns false otherwise.
119
     *
120
     * @param string $name Identifier of the entry to look for.
121
     *
122
     * @return bool
123
     */
124
    public function has($name)
125
    {
126 View Code Duplication
        if (!is_string($name)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
            throw new \InvalidArgumentException(sprintf(
128
                'The name parameter must be of type string, %s given',
129
                is_object($name) ? get_class($name) : gettype($name)
130
            ));
131
        }
132
133
        $serviceName = strtolower($name);
134
135
        if ($this->hasAlias($serviceName)) {
136
            $serviceName = $this->aliasDefinitions[$serviceName];
137
        }
138
139
        return isset($this->services[$serviceName]) || array_key_exists($serviceName, $this->services);
140
    }
141
142
    /**
143
     * Define an object in the container.
144
     *
145
     * @param string $name  Entry name
146
     * @param mixed  $value Value
147
     */
148
    public function set($name, $value)
149
    {
150
        $name      = strtolower($name);
151
        $isClosure = false;
152
153
        if ($value instanceof \Closure) {
154
            $this->useServiceFactory     = true;
155
            $isClosure                   = true;
156
            $this->serviceFactory[$name] = $value;
157
            unset($this->services[$name]);
158
        }
159
160
        if (!$isClosure) {
161
            $this->services[$name] = $value;
162
        }
163
    }
164
165
    /**
166
     * @param string $name
167
     *
168
     * @return mixed
169
     */
170
    private function getAlias($name)
171
    {
172
        if (!$this->hasAlias($name)) {
173
            throw new \InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $name));
174
        }
175
176
        return $this->aliasDefinitions[$name];
177
    }
178
179
    /**
180
     * @param string $name
181
     *
182
     * @return bool
183
     */
184
    private function hasAlias($name)
185
    {
186
        return isset($this->aliasDefinitions[$name]);
187
    }
188
189
    /**
190
     * @param string $name
191
     */
192
    private function setAlias($name)
193
    {
194
        $alias = new AliasDefinition();
195
        $alias->aliasFromNamespace($name);
196
197
        $this->aliasDefinitions[$alias->getTargetName()] = $alias->getName();
198
    }
199
200
    /**
201
     * Get service from Closure object.
202
     *
203
     * @param string $name
204
     *
205
     * @return mixed
206
     */
207
    private function getServiceFromFactory($name)
208
    {
209
        $serviceFactory    = $this->serviceFactory[$name];
210
        $delegateContainer = $this->delegateContainer ?: $this;
211
212
        return $serviceFactory($delegateContainer);
213
    }
214
215
    private function hasServiceInFactory($name)
216
    {
217
        return isset($this->serviceFactory[$name]);
218
    }
219
}
220