Container::getClosure()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 13
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
        static::$instance = $this;
102
    }
103
104
    /**
105
     *
106
     * @param ResolverInterface $resolver
107
     * @return $this
108
     */
109
    public function setResolver(ResolverInterface $resolver): self
110
    {
111
        $this->resolver = $resolver;
112
113
        return $this;
114
    }
115
116
    /**
117
     *
118
     * @param StorageCollection $storage
119
     * @return $this
120
     */
121
    public function setStorage(StorageCollection $storage): self
122
    {
123
        $this->storage = $storage;
124
125
        return $this;
126
    }
127
128
    /**
129
     * Return the global instance of the container
130
     * @return self
131
     */
132
    public static function getInstance(): self
133
    {
134
        if (static::$instance === null) {
135
            static::$instance = new self();
136
        }
137
138
        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...
139
    }
140
141
    /**
142
     * Remove all lock when copy this object
143
     * @return void
144
     */
145
    public function __clone(): void
146
    {
147
        $this->lock = [];
148
    }
149
150
    /**
151
     * Return the resolver instance
152
     * @return ResolverInterface
153
     */
154
    public function getResolver(): ResolverInterface
155
    {
156
        return $this->resolver;
157
    }
158
159
    /**
160
     * Return the storage collection instance
161
     * @return StorageCollection
162
     */
163
    public function getStorage(): StorageCollection
164
    {
165
        return $this->storage;
166
    }
167
168
    /**
169
     * Return the array of resolved instances
170
     * @return array<string, object>
171
     */
172
    public function getInstances(): array
173
    {
174
        return $this->instances;
175
    }
176
177
    /**
178
     * Bind new type to the container
179
     * @param  string $id the id of the type to bind
180
     * @param  Closure|string|mixed|null  $type the type to bind. if null will
181
     * use the $id
182
     * @param  array<string, mixed>  $parameters the array of parameters
183
     * used to resolve instance of the type
184
     * @param  bool $shared  whether the instance need to be shared
185
     * @return StorageInterface
186
     */
187
    public function bind(
188
        string $id,
189
        mixed $type = null,
190
        array $parameters = [],
191
        bool $shared = false
192
    ): StorageInterface {
193
        //remove the previous instance
194
        unset($this->instances[$id]);
195
196
        /** @var mixed */
197
        $resolvedType = $type ?? $id;
198
        if (!($resolvedType instanceof Closure)) {
199
            $resolvedType = $this->getClosure($resolvedType);
200
        }
201
202
        $params = [];
203
        foreach ($parameters as $name => $value) {
204
            $params[] = new Parameter($name, $value);
205
        }
206
207
        return $this->storage->add(new Storage(
208
            $id,
209
            $resolvedType,
210
            $shared,
211
            count($params) > 0 ? new ParameterCollection($params) : null
212
        ));
213
    }
214
215
    /**
216
     * Set the new instance in to the container
217
     * @param  object  $instance the instance to set
218
     * @param  string|null $id  the id of the instance. If null will try
219
     * to detect the type using get_class()
220
     * @return void
221
     */
222
    public function instance(object $instance, ?string $id = null): void
223
    {
224
        if ($id === null) {
225
            $id = get_class($instance);
226
        }
227
        $this->storage->delete($id);
228
        $this->instances[$id] = $instance;
229
    }
230
231
    /**
232
     * Bind the type as shared
233
     * @param  string $id
234
     * @param  Closure|string|mixed|null $type       the type to share
235
     * @param  array<string, mixed>  $parameters
236
     * @return StorageInterface
237
     */
238
    public function share(string $id, mixed $type = null, array $parameters = []): StorageInterface
239
    {
240
        return $this->bind($id, $type, $parameters, true);
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246
    public function has(string $id): bool
247
    {
248
        return isset($this->instances[$id]) || $this->storage->has($id);
249
    }
250
251
    /**
252
     * Make the instance for the given type.
253
     *
254
     * The difference with get($id) is that if instance is not yet available in container
255
     * it will be make.
256
     *
257
     * @param  string $id
258
     * @param  array<string, mixed>  $parameters
259
     * @return mixed
260
     */
261
    public function make(string $id, array $parameters = []): mixed
262
    {
263
        if (isset($this->lock[$id])) {
264
            throw new ContainerException(sprintf(
265
                'Detected a cyclic dependency while provisioning [%s]',
266
                $id
267
            ));
268
        }
269
270
        $this->lock[$id] = count($this->lock);
271
272
        if ($this->has($id) === false) {
273
            $this->bind($id, null, $parameters, false);
274
        }
275
276
        if (isset($this->instances[$id])) {
277
            unset($this->lock[$id]);
278
279
            return $this->instances[$id];
280
        }
281
282
        $instance = null;
283
        $result = $this->storage->get($id);
284
        if ($result !== null) {
285
            /** @var mixed */
286
            $instance = $result->getInstance($this);
287
288
            if ($result->isShared()) {
289
                $this->instances[$id] = $instance;
290
            }
291
            unset($this->lock[$id]);
292
        }
293
294
        return $instance;
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    public function get(string $id): mixed
301
    {
302
        if ($this->has($id) === false) {
303
            throw new NotFoundException(sprintf('The type/class [%s] does not exist in the container!', $id));
304
        }
305
306
        return $this->make($id);
307
    }
308
309
    /**
310
     * Return the closure for the given type
311
     * @param  mixed $type
312
     * @return Closure
313
     */
314
    protected function getClosure(mixed $type): Closure
315
    {
316
        if (is_callable($type)) {
317
            return Closure::fromCallable($type);
318
        } elseif (is_string($type) === false) {
319
            return function () use ($type) {
320
                return $type;
321
            };
322
        }
323
324
        return function ($container, $parameters) use ($type) {
325
            return $container->getResolver()
326
                             ->resolve($container, $type, $parameters);
327
        };
328
    }
329
}
330