Passed
Push — develop ( 0a189f...1b174b )
by nguereza
15:41 queued 03:57
created

Container::getStorage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * Platine Container
5
 *
6
 * Platine Container is the implementation of PSR 11
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Container
11
 * Copyright (c) 2019 Dion Chaika
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file Container.php
34
 *
35
 *  The Container class used to handle dependency injection and keep instances of
36
 *  loaded classes, etc.
37
 *
38
 *  @package    Platine\Container
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   https://www.platine-php.com
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Container;
50
51
use Closure;
52
use Platine\Container\Exception\ContainerException;
53
use Platine\Container\Exception\NotFoundException;
54
use Platine\Container\Resolver\ConstructorResolver;
55
use Platine\Container\Resolver\ResolverInterface;
56
57
/**
58
 * @class Container
59
 * @package Platine\Container
60
 */
61
class Container implements ContainerInterface
62
{
63
    /**
64
     * The container global instance
65
     * @var Container|null
66
     */
67
    protected static ?Container $instance = null;
68
69
    /**
70
     * The resolver instance
71
     * @var ResolverInterface
72
     */
73
    protected ResolverInterface $resolver;
74
75
    /**
76
     * The Storage collection instance
77
     * @var StorageCollection
78
     */
79
    protected StorageCollection $storage;
80
81
    /**
82
     * The list of resolved instances
83
     * @var array<string, object>
84
     */
85
    protected array $instances = [];
86
87
    /**
88
     * The current instances in phase of construction
89
     * @var array<string, int>
90
     */
91
    protected array $lock = [];
92
93
    /**
94
     * Create new container instance
95
     */
96
    public function __construct()
97
    {
98
        $this->resolver =  new ConstructorResolver();
99
        $this->storage =  new StorageCollection();
100
    }
101
102
    /**
103
     *
104
     * @param ResolverInterface $resolver
105
     * @return $this
106
     */
107
    public function setResolver(ResolverInterface $resolver): self
108
    {
109
        $this->resolver = $resolver;
110
111
        return $this;
112
    }
113
114
    /**
115
     *
116
     * @param StorageCollection $storage
117
     * @return $this
118
     */
119
    public function setStorage(StorageCollection $storage): self
120
    {
121
        $this->storage = $storage;
122
123
        return $this;
124
    }
125
126
    /**
127
     * Return the global instance of the container
128
     * @return self
129
     */
130
    public static function getInstance(): self
131
    {
132
        if (static::$instance === null) {
133
            static::$instance = new self();
134
        }
135
136
        return static::$instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::instance could return the type null which is incompatible with the type-hinted return Platine\Container\Container. Consider adding an additional type-check to rule them out.
Loading history...
137
    }
138
139
    /**
140
     * Remove all lock when copy this object
141
     * @return void
142
     */
143
    public function __clone(): void
144
    {
145
        $this->lock = [];
146
    }
147
148
    /**
149
     * Return the resolver instance
150
     * @return ResolverInterface
151
     */
152
    public function getResolver(): ResolverInterface
153
    {
154
        return $this->resolver;
155
    }
156
157
    /**
158
     * Return the storage collection instance
159
     * @return StorageCollection
160
     */
161
    public function getStorage(): StorageCollection
162
    {
163
        return $this->storage;
164
    }
165
166
    /**
167
     * Return the array of resolved instances
168
     * @return array<string, object>
169
     */
170
    public function getInstances(): array
171
    {
172
        return $this->instances;
173
    }
174
175
    /**
176
     * Bind new type to the container
177
     * @param  string $id the id of the type to bind
178
     * @param  Closure|string|mixed|null  $type the type to bind. if null will
179
     * use the $id
180
     * @param  array<string, mixed>  $parameters the array of parameters
181
     * used to resolve instance of the type
182
     * @param  bool $shared  whether the instance need to be shared
183
     * @return StorageInterface
184
     */
185
    public function bind(
186
        string $id,
187
        mixed $type = null,
188
        array $parameters = [],
189
        bool $shared = false
190
    ): StorageInterface {
191
        //remove the previous instance
192
        unset($this->instances[$id]);
193
194
        /** @var mixed */
195
        $resolvedType = $type ?? $id;
196
        if (!($resolvedType instanceof Closure)) {
197
            $resolvedType = $this->getClosure($resolvedType);
198
        }
199
200
        $params = [];
201
        foreach ($parameters as $name => $value) {
202
            $params[] = new Parameter($name, $value);
203
        }
204
205
        return $this->storage->add(new Storage(
206
            $id,
207
            $resolvedType,
208
            $shared,
209
            !empty($params) ? new ParameterCollection($params) : null
210
        ));
211
    }
212
213
    /**
214
     * Set the new instance in to the container
215
     * @param  object  $instance the instance to set
216
     * @param  string|null $id  the id of the instance. If null will try
217
     * to detect the type using get_class()
218
     * @return void
219
     */
220
    public function instance(object $instance, ?string $id = null): void
221
    {
222
        if ($id === null) {
223
            $id = get_class($instance);
224
        }
225
        $this->storage->delete($id);
226
        $this->instances[$id] = $instance;
227
    }
228
229
    /**
230
     * Bind the type as shared
231
     * @param  string $id
232
     * @param  Closure|string|mixed|null $type       the type to share
233
     * @param  array<string, mixed>  $parameters
234
     * @return StorageInterface
235
     */
236
    public function share(string $id, mixed $type = null, array $parameters = []): StorageInterface
237
    {
238
        return $this->bind($id, $type, $parameters, true);
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function has(string $id): bool
245
    {
246
        return isset($this->instances[$id]) || $this->storage->has($id);
247
    }
248
249
    /**
250
     * Make the instance for the given type.
251
     *
252
     * The difference with get($id) is that if instance is not yet available in container
253
     * it will be make.
254
     *
255
     * @param  string $id
256
     * @param  array<string, mixed>  $parameters
257
     * @return mixed
258
     */
259
    public function make(string $id, array $parameters = []): mixed
260
    {
261
        if (isset($this->lock[$id])) {
262
            throw new ContainerException(sprintf(
263
                'Detected a cyclic dependency while provisioning [%s]',
264
                $id
265
            ));
266
        }
267
268
        $this->lock[$id] = count($this->lock);
269
270
        if ($this->has($id) === false) {
271
            $this->bind($id, null, $parameters, false);
272
        }
273
274
        if (isset($this->instances[$id])) {
275
            unset($this->lock[$id]);
276
277
            return $this->instances[$id];
278
        }
279
280
        $instance = null;
281
        $result = $this->storage->get($id);
282
        if ($result !== null) {
283
            /** @var mixed */
284
            $instance = $result->getInstance($this);
285
286
            if ($result->isShared()) {
287
                $this->instances[$id] = $instance;
288
            }
289
            unset($this->lock[$id]);
290
        }
291
292
        return $instance;
293
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298
    public function get(string $id): mixed
299
    {
300
        if ($this->has($id) === false) {
301
            throw new NotFoundException(sprintf('The type/class [%s] does not exist in the container!', $id));
302
        }
303
304
        return $this->make($id);
305
    }
306
307
    /**
308
     * Return the closure for the given type
309
     * @param  mixed $type
310
     * @return Closure
311
     */
312
    protected function getClosure(mixed $type): Closure
313
    {
314
        if (is_callable($type)) {
315
            return Closure::fromCallable($type);
316
        } elseif (!is_string($type)) {
317
            return function () use ($type) {
318
                return $type;
319
            };
320
        }
321
322
        return function ($container, $parameters) use ($type) {
323
            return $container->getResolver()
324
                             ->resolve($container, $type, $parameters);
325
        };
326
    }
327
}
328