Completed
Push — master ( b57571...eaf5ea )
by Michael
06:33
created

Container   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 93.75%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 1
dl 0
loc 228
ccs 60
cts 64
cp 0.9375
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
B extend() 0 18 5
A factory() 0 6 1
A keys() 0 4 1
A offsetExists() 0 4 2
B offsetGet() 0 21 7
A offsetSet() 0 8 3
A offsetUnset() 0 9 3
A protect() 0 6 1
A raw() 0 10 3
A register() 0 8 2
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains class Container.
5
 *
6
 * This should act as a drop-in replacement for Pimple. In the end the wrapper
7
 * idea just did not work do to the design of Pimple so I re-implemented
8
 * everything here and use my interface.
9
 *
10
 * PHP version 7.0+
11
 *
12
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
13
 * which can be used to access the Eve Online API data and place it into a
14
 * database.
15
 * Copyright (C) 2014-2016 Michael Cummings
16
 *
17
 * This program is free software: you can redistribute it and/or modify it
18
 * under the terms of the GNU Lesser General Public License as published by the
19
 * Free Software Foundation, either version 3 of the License, or (at your
20
 * option) any later version.
21
 *
22
 * This program is distributed in the hope that it will be useful, but WITHOUT
23
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
25
 * for more details.
26
 *
27
 * You should have received a copy of the GNU Lesser General Public License
28
 * along with this program. If not, see
29
 * <http://spdx.org/licenses/LGPL-3.0.html>.
30
 *
31
 * You should be able to find a copy of this license in the COPYING-LESSER.md
32
 * file. A copy of the GNU GPL should also be available in the COPYING.md file.
33
 *
34
 * @copyright 2014-2016 Michael Cummings
35
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
36
 * @author    Michael Cummings <[email protected]>
37
 * @author    Fabien Potencier
38
 */
39
namespace Yapeal\Container;
40
41
/**
42
 * Container class.
43
 *
44
 * @author Michael Cummings <[email protected]>
45
 * @since  1.1.x-WIP
46
 */
47
class Container implements ContainerInterface
48
{
49
    /**
50
     * Instantiate the container.
51
     *
52
     * Objects and parameters can be passed as argument to the constructor.
53
     *
54
     * @param array $values The parameters or objects.
55
     *
56
     * @throws \RuntimeException
57
     */
58 28
    public function __construct(array $values = [])
59
    {
60 28
        $this->factories = new \SplObjectStorage();
61 28
        $this->protected = new \SplObjectStorage();
62 28
        foreach ($values as $key => $value) {
63 1
            $this->offsetSet($key, $value);
64
        }
65
    }
66
    /**
67
     * Extends an object definition.
68
     *
69
     * Useful when you want to extend an existing object definition,
70
     * without necessarily loading that object.
71
     *
72
     * @param string   $id       The unique identifier for the object
73
     * @param callable $callable A service definition to extend the original
74
     *
75
     * @return callable The wrapped callable
76
     *
77
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
78
     */
79 5
    public function extend(string $id, callable $callable): callable
80
    {
81 5
        if (!$this->offsetExists($id)) {
82 1
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
83
        }
84 4
        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
85 2
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
86
        }
87 3
        $factory = $this->values[$id];
88 3
        $extended = function ($c) use ($callable, $factory) {
89 3
            return $callable($factory($c), $c);
90 3
        };
91 3
        if (isset($this->factories[$factory])) {
92
            $this->factories->detach($factory);
93
            $this->factories->attach($extended);
94
        }
95 3
        return $this[$id] = $extended;
96
    }
97
    /**
98
     * Marks a callable as being a factory service.
99
     *
100
     * @param callable $callable A service definition to be used as a factory
101
     *
102
     * @return callable The passed callable
103
     *
104
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
105
     */
106 2
    public function factory(callable $callable): callable
107
    {
108
        /** @noinspection PhpParamsInspection */
109 2
        $this->factories->attach($callable);
110 2
        return $callable;
111
    }
112
    /**
113
     * Returns all defined value names.
114
     *
115
     * @return array An array of value names
116
     */
117 4
    public function keys(): array
118
    {
119 4
        return array_keys($this->keys);
120
    }
121
    /**
122
     * Checks if a parameter or an object is set.
123
     *
124
     * @param string $id The unique identifier for the parameter or object
125
     *
126
     * @return bool
127
     */
128 25
    public function offsetExists($id)
129
    {
130 25
        return (array_key_exists($id, $this->keys) && true === $this->keys[$id]);
131
    }
132
    /**
133
     * Gets a parameter or an object.
134
     *
135
     * @param string $id The unique identifier for the parameter or object
136
     *
137
     * @return mixed The value of the parameter or an object
138
     *
139
     * @throws \InvalidArgumentException if the identifier is not defined
140
     */
141 19
    public function offsetGet($id)
142
    {
143 19
        if (!$this->offsetExists($id)) {
144 2
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
145
        }
146 17
        if (array_key_exists($id, $this->raw)
147 17
            || !is_object($this->values[$id])
148 17
            || isset($this->protected[$this->values[$id]])
149 17
            || !method_exists($this->values[$id], '__invoke')
150
        ) {
151 8
            return $this->values[$id];
152
        }
153 11
        if (isset($this->factories[$this->values[$id]])) {
154 2
            return $this->values[$id]($this);
155
        }
156 9
        $raw = $this->values[$id];
157 9
        $val = $this->values[$id] = $raw($this);
158 9
        $this->raw[$id] = $raw;
159 9
        $this->frozen[$id] = true;
160 9
        return $val;
161
    }
162
    /**
163
     * Sets a parameter or an object.
164
     *
165
     * Objects must be defined as Closures.
166
     *
167
     * Allowing any PHP callable leads to difficult to debug problems
168
     * as function names (strings) are callable (creating a function with
169
     * the same name as an existing parameter would break your container).
170
     *
171
     * @param string $id    The unique identifier for the parameter or object
172
     * @param mixed  $value The value of the parameter or a closure to define an object
173
     *
174
     * @throws \RuntimeException Prevent override of a frozen service
175
     */
176 23
    public function offsetSet($id, $value)
177
    {
178 23
        if (array_key_exists($id, $this->frozen) && true === $this->frozen[$id]) {
179 1
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
180
        }
181 23
        $this->values[$id] = $value;
182 23
        $this->keys[$id] = true;
183
    }
184
    /**
185
     * Unsets a parameter or an object.
186
     *
187
     * @param string $id The unique identifier for the parameter or object
188
     */
189 3
    public function offsetUnset($id)
190
    {
191 3
        if ($this->offsetExists($id)) {
192 3
            if (is_object($this->values[$id])) {
193 2
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
194
            }
195 3
            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
196
        }
197
    }
198
    /**
199
     * Protects a callable from being interpreted as a service.
200
     *
201
     * This is useful when you want to store a callable as a parameter.
202
     *
203
     * @param callable $callable A callable to protect from being evaluated
204
     *
205
     * @return callable The passed callable
206
     *
207
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
208
     */
209 1
    public function protect(callable $callable): callable
210
    {
211
        /** @noinspection PhpParamsInspection */
212 1
        $this->protected->attach($callable);
213 1
        return $callable;
214
    }
215
    /**
216
     * Gets a parameter or the closure defining an object.
217
     *
218
     * @param string $id The unique identifier for the parameter or object
219
     *
220
     * @return mixed The value of the parameter or the closure defining an object
221
     *
222
     * @throws \InvalidArgumentException if the identifier is not defined
223
     */
224 3
    public function raw(string $id)
225
    {
226 3
        if (!$this->offsetExists($id)) {
227 1
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
228
        }
229 2
        if (array_key_exists($id, $this->raw)) {
230
            return $this->raw[$id];
231
        }
232 2
        return $this->values[$id];
233
    }
234
    /**
235
     * Registers a service provider.
236
     *
237
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
238
     * @param array                    $values   An array of values that customizes the provider
239
     *
240
     * @return ContainerInterface
241
     */
242 1
    public function register(ServiceProviderInterface $provider, array $values = []): ContainerInterface
243
    {
244 1
        $provider->register($this);
245 1
        foreach ($values as $key => $value) {
246
            $this[(string)$key] = $value;
247
        }
248 1
        return $this;
249
    }
250
    /**
251
     * @var \SplObjectStorage $factories
252
     */
253
    private $factories;
254
    /**
255
     * @var bool[] $frozen
256
     */
257
    private $frozen = [];
258
    /**
259
     * @var bool[] $keys
260
     */
261
    private $keys = [];
262
    /**
263
     * @var \SplObjectStorage $protected
264
     */
265
    private $protected;
266
    /**
267
     * @var mixed[] $raw
268
     */
269
    private $raw = [];
270
    /**
271
     * @var mixed[] $values
272
     */
273
    private $values = [];
274
}
275