Completed
Push — master ( 2d4410...211342 )
by Michael
05:20
created

Container::offsetUnset()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 3
eloc 5
nc 3
nop 1
1
<?php
2
/**
3
 * Contains class Container.
4
 *
5
 * This should act as a drop-in replacement for Pimple. In the end the wrapper
6
 * idea just did not work do to the design of Pimple so I re-implemented
7
 * everything here and use my interface.
8
 *
9
 * PHP version 5.5
10
 *
11
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
12
 * which can be used to access the Eve Online API data and place it into a
13
 * database.
14
 * Copyright (C) 2014-2016 Michael Cummings
15
 *
16
 * This program is free software: you can redistribute it and/or modify it
17
 * under the terms of the GNU Lesser General Public License as published by the
18
 * Free Software Foundation, either version 3 of the License, or (at your
19
 * option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful, but WITHOUT
22
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
24
 * for more details.
25
 *
26
 * You should have received a copy of the GNU Lesser General Public License
27
 * along with this program. If not, see
28
 * <http://www.gnu.org/licenses/>.
29
 *
30
 * You should be able to find a copy of this license in the LICENSE.md file. A
31
 * copy of the GNU GPL should also be available in the GNU-GPL.md file.
32
 *
33
 * @copyright 2014-2016 Michael Cummings
34
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
35
 * @author    Michael Cummings <[email protected]>
36
 * @author    Fabien Potencier
37
 */
38
namespace Yapeal\Container;
39
40
/**
41
 * Container class.
42
 *
43
 * @author Michael Cummings <[email protected]>
44
 * @since  1.1.x-WIP
45
 */
46
class Container implements ContainerInterface
47
{
48
    /**
49
     * Instantiate the container.
50
     *
51
     * Objects and parameters can be passed as argument to the constructor.
52
     *
53
     * @param array $values The parameters or objects.
54
     *
55
     * @throws \RuntimeException
56
     */
57
    public function __construct(array $values = [])
58
    {
59
        $this->factories = new \SplObjectStorage();
60
        $this->protected = new \SplObjectStorage();
61
        foreach ($values as $key => $value) {
62
            $this->offsetSet($key, $value);
63
        }
64
    }
65
    /**
66
     * Extends an object definition.
67
     *
68
     * Useful when you want to extend an existing object definition,
69
     * without necessarily loading that object.
70
     *
71
     * @param string   $id       The unique identifier for the object
72
     * @param callable $callable A service definition to extend the original
73
     *
74
     * @return callable The wrapped callable
75
     *
76
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
77
     */
78
    public function extend($id, $callable)
79
    {
80
        if (!$this->offsetExists($id)) {
81
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
82
        }
83
        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
84
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
85
        }
86
        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
87
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
88
        }
89
        $factory = $this->values[$id];
90
        $extended = function ($c) use ($callable, $factory) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $c. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
91
            return $callable($factory($c), $c);
92
        };
93
        if (isset($this->factories[$factory])) {
94
            $this->factories->detach($factory);
95
            $this->factories->attach($extended);
96
        }
97
        return $this[$id] = $extended;
98
    }
99
    /**
100
     * Marks a callable as being a factory service.
101
     *
102
     * @param callable $callable A service definition to be used as a factory
103
     *
104
     * @return callable The passed callable
105
     *
106
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
107
     */
108
    public function factory($callable)
109
    {
110
        if (!method_exists($callable, '__invoke')) {
111
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
112
        }
113
        /** @noinspection PhpParamsInspection */
114
        $this->factories->attach($callable);
115
        return $callable;
116
    }
117
    /**
118
     * Returns all defined value names.
119
     *
120
     * @return array An array of value names
121
     */
122
    public function keys()
123
    {
124
        return array_keys($this->values);
125
    }
126
    /**
127
     * Checks if a parameter or an object is set.
128
     *
129
     * @param string $id The unique identifier for the parameter or object
130
     *
131
     * @return bool
132
     */
133
    public function offsetExists($id)
134
    {
135
        return (array_key_exists($id, $this->keys) && true === $this->keys[$id]);
136
    }
137
    /**
138
     * Gets a parameter or an object.
139
     *
140
     * @param string $id The unique identifier for the parameter or object
141
     *
142
     * @return mixed The value of the parameter or an object
143
     *
144
     * @throws \InvalidArgumentException if the identifier is not defined
145
     */
146
    public function offsetGet($id)
147
    {
148
        if (!$this->offsetExists($id)) {
149
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
150
        }
151
        if (array_key_exists($id, $this->raw)
152
            || !is_object($this->values[$id])
153
            || isset($this->protected[$this->values[$id]])
154
            || !method_exists($this->values[$id], '__invoke')
155
        ) {
156
            return $this->values[$id];
157
        }
158
        if (isset($this->factories[$this->values[$id]])) {
159
            return $this->values[$id]($this);
160
        }
161
        $raw = $this->values[$id];
162
        $val = $this->values[$id] = $raw($this);
163
        $this->raw[$id] = $raw;
164
        $this->frozen[$id] = true;
165
        return $val;
166
    }
167
    /**
168
     * Sets a parameter or an object.
169
     *
170
     * Objects must be defined as Closures.
171
     *
172
     * Allowing any PHP callable leads to difficult to debug problems
173
     * as function names (strings) are callable (creating a function with
174
     * the same name as an existing parameter would break your container).
175
     *
176
     * @param string $id    The unique identifier for the parameter or object
177
     * @param mixed  $value The value of the parameter or a closure to define an object
178
     *
179
     * @throws \RuntimeException Prevent override of a frozen service
180
     */
181
    public function offsetSet($id, $value)
182
    {
183
        if (array_key_exists($id, $this->frozen) && true === $this->frozen[$id]) {
184
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
185
        }
186
        $this->values[$id] = $value;
187
        $this->keys[$id] = true;
188
    }
189
    /**
190
     * Unsets a parameter or an object.
191
     *
192
     * @param string $id The unique identifier for the parameter or object
193
     */
194
    public function offsetUnset($id)
195
    {
196
        if ($this->offsetExists($id)) {
197
            if (is_object($this->values[$id])) {
198
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
199
            }
200
            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
201
        }
202
    }
203
    /**
204
     * Protects a callable from being interpreted as a service.
205
     *
206
     * This is useful when you want to store a callable as a parameter.
207
     *
208
     * @param callable $callable A callable to protect from being evaluated
209
     *
210
     * @return callable The passed callable
211
     *
212
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
213
     */
214
    public function protect($callable)
215
    {
216
        if (!method_exists($callable, '__invoke')) {
217
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
218
        }
219
        /** @noinspection PhpParamsInspection */
220
        $this->protected->attach($callable);
221
        return $callable;
222
    }
223
    /**
224
     * Gets a parameter or the closure defining an object.
225
     *
226
     * @param string $id The unique identifier for the parameter or object
227
     *
228
     * @return mixed The value of the parameter or the closure defining an object
229
     *
230
     * @throws \InvalidArgumentException if the identifier is not defined
231
     */
232
    public function raw($id)
233
    {
234
        if (!$this->offsetExists($id)) {
235
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
236
        }
237
        if (array_key_exists($id, $this->raw)) {
238
            return $this->raw[$id];
239
        }
240
        return $this->values[$id];
241
    }
242
    /**
243
     * @var \SplObjectStorage $factories
244
     */
245
    private $factories;
246
    /**
247
     * @var bool[] $frozen
248
     */
249
    private $frozen = [];
250
    /**
251
     * @var bool[] $keys
252
     */
253
    private $keys = [];
254
    /**
255
     * @var \SplObjectStorage $protected
256
     */
257
    private $protected;
258
    /**
259
     * @var mixed[] $raw
260
     */
261
    private $raw = [];
262
    /**
263
     * @var mixed[] $values
264
     */
265
    private $values = [];
266
}
267