Passed
Push — develop ( 917df1...3c0663 )
by nguereza
09:21 queued 07:09
created

Container::bindAbstract()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 11
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A Container::instance() 0 7 2
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
55
class Container implements ContainerInterface
56
{
0 ignored issues
show
Coding Style introduced by
Opening brace must not be followed by a blank line
Loading history...
57
58
    /**
59
     * The container global instance
60
     * @var Container|null
61
     */
62
    protected static ?Container $instance = null;
63
64
    /**
65
     * The resolver instance
66
     * @var ResolverInterface
67
     */
68
    protected ResolverInterface $resolver;
69
70
    /**
71
     * The Storage collection instance
72
     * @var StorageCollection
73
     */
74
    protected StorageCollection $storages;
75
76
    /**
77
     * The list of resolved instances
78
     * @var array<string, object>
79
     */
80
    protected array $instances = [];
81
82
    /**
83
     * The current instances in phase of construction
84
     * @var array<string, int>
85
     */
86
    protected array $lock = [];
87
88
    /**
89
     * Create new container instance
90
     */
91
    public function __construct()
92
    {
93
        $this->resolver =  new ConstructorResolver();
94
        $this->storages =  new StorageCollection();
95
        self::$instance = $this;
96
    }
97
98
    /**
99
     *
100
     * @param ResolverInterface $resolver
101
     * @return $this
102
     */
103
    public function setResolver(ResolverInterface $resolver): self
104
    {
105
        $this->resolver = $resolver;
106
107
        return $this;
108
    }
109
110
    /**
111
     *
112
     * @param StorageCollection $storages
113
     * @return $this
114
     */
115
    public function setStorages(StorageCollection $storages): self
116
    {
117
        $this->storages = $storages;
118
119
        return $this;
120
    }
121
122
    /**
123
     * Return the global instance of the container
124
     * @return $this
125
     */
126
    public static function getInstance(): self
127
    {
128
        if (static::$instance === null) {
129
            static::$instance = new static();
130
        }
131
132
        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...
133
    }
134
135
    /**
136
     * Remove all lock when copy this object
137
     * @return void
138
     */
139
    public function __clone()
140
    {
141
        $this->lock = [];
142
    }
143
144
    /**
145
     * Return the resolver instance
146
     * @return ResolverInterface
147
     */
148
    public function getResolver(): ResolverInterface
149
    {
150
        return $this->resolver;
151
    }
152
153
    /**
154
     * Return the storage collection instance
155
     * @return StorageCollection
156
     */
157
    public function getStorages(): StorageCollection
158
    {
159
        return $this->storages;
160
    }
161
162
    /**
163
     * Return the array of resolved instances
164
     * @return array<string, object>
165
     */
166
    public function getInstances(): array
167
    {
168
        return $this->instances;
169
    }
170
171
    /**
172
     * Bind new type to the container
173
     * @param  string $id the id of the type to bind
174
     * @param  Closure|string|mixed|null  $type the type to bind. if null will
175
     * use the $id
176
     * @param  array<string, mixed>  $parameters the array of parameters
177
     * used to resolve instance of the type
178
     * @param  bool $shared  whether the instance need to be shared
179
     * @return StorageInterface
180
     */
181
    public function bind(
182
        string $id,
183
        $type = null,
184
        array $parameters = [],
185
        bool $shared = false
186
    ): StorageInterface {
187
        //remove the previous instance
188
        unset($this->instances[$id]);
189
190
        /** @var mixed */
191
        $type = $type ? $type : $id;
192
        if (!($type instanceof Closure)) {
193
            $type = $this->getClosure($type);
194
        }
195
196
        $params = [];
197
        foreach ($parameters as $name => $value) {
198
            $params[] = new Parameter($name, $value);
199
        }
200
201
        return $this->storages->add(new Storage(
202
            $id,
203
            $type,
204
            $shared,
205
            !empty($params) ? new ParameterCollection($params) : null
206
        ));
207
    }
208
209
    /**
210
     * Set the new instance in to the contaner
211
     * @param  object  $instance the instance to set
212
     * @param  string|null $id  the id of the instance. If null will try
213
     * to detect the type using get_class()
214
     * @return void
215
     */
216
    public function instance(object $instance, ?string $id = null): void
217
    {
218
        if ($id === null) {
219
            $id = get_class($instance);
220
        }
221
        $this->storages->delete($id);
222
        $this->instances[$id] = $instance;
223
    }
224
225
    /**
226
     * Bind the type as shared
227
     * @param  string $id
228
     * @param  Closure|string|mixed|null $type       the type to share
229
     * @param  array<string, mixed>  $parameters
230
     * @return StorageInterface
231
     */
232
    public function share(string $id, $type = null, array $parameters = []): StorageInterface
233
    {
234
        return $this->bind($id, $type, $parameters, true);
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function has(string $id): bool
241
    {
242
        return isset($this->instances[$id]) || $this->storages->has($id);
243
    }
244
245
    /**
246
     * Make the instance for the given type.
247
     *
248
     * The difference with get($id) is that if instance is not yet available in container
249
     * it will be make.
250
     *
251
     * @param  string $id
252
     * @param  array<string, mixed>  $parameters
253
     * @return mixed
254
     */
255
    public function make(string $id, array $parameters = [])
256
    {
257
        if (isset($this->lock[$id])) {
258
            throw new ContainerException(sprintf(
259
                'Detected a cyclic dependency while provisioning [%s]',
260
                $id
261
            ));
262
        }
263
        $this->lock[$id] = count($this->lock);
264
265
        if (!$this->has($id)) {
266
            $this->bind($id, null, $parameters, false);
267
        }
268
269
        if (isset($this->instances[$id])) {
270
            unset($this->lock[$id]);
271
            return $this->instances[$id];
272
        }
273
274
        /** @var mixed */
275
        $instance = $this->storages
276
                ->get($id)
277
                ->getInstance($this);
278
279
        if ($this->storages->get($id)->isShared()) {
280
            $this->instances[$id] = $instance;
281
        }
282
        unset($this->lock[$id]);
283
284
        return $instance;
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function get(string $id)
291
    {
292
        if (!$this->has($id)) {
293
            throw new NotFoundException(sprintf('The type/class [%s] does not exist in the container!', $id));
294
        }
295
296
        return $this->make($id);
297
    }
298
299
    /**
300
     * Return the closure for the given type
301
     * @param  string|mixed $type
302
     * @return Closure
303
     */
304
    protected function getClosure($type): Closure
305
    {
306
        if (is_callable($type)) {
307
            return Closure::fromCallable($type);
308
        } elseif (!is_string($type)) {
309
            return function () use ($type) {
310
                return $type;
311
            };
312
        }
313
314
        return function ($container, $parameters) use ($type) {
315
            return $container
316
                    ->getResolver()
317
                    ->resolve($container, $type, $parameters);
318
        };
319
    }
320
}
321