Container::offsetGet()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.5546
c 0
b 0
f 0
cc 7
nc 4
nop 1
1
<?php
2
namespace PSB\Core\ObjectBuilder;
3
4
/**
5
 * This is the Pimple container version 3.0.2 found at https://github.com/silexphp/Pimple
6
 *
7
 * It was included permanently in the codebase in order to avoid dependency conflicts with
8
 * projects that rely on lower versions.
9
 *
10
 * @author  Fabien Potencier
11
 * @codeCoverageIgnore
12
 */
13
class Container implements \ArrayAccess
14
{
15
    private $values = [];
16
    private $factories;
17
    private $protected;
18
    private $frozen = [];
19
    private $raw = [];
20
    private $keys = [];
21
22
    /**
23
     * Instantiate the container.
24
     *
25
     * Objects and parameters can be passed as argument to the constructor.
26
     *
27
     * @param array $values The parameters or objects.
28
     */
29
    public function __construct(array $values = [])
30
    {
31
        $this->factories = new \SplObjectStorage();
32
        $this->protected = new \SplObjectStorage();
33
34
        foreach ($values as $key => $value) {
35
            $this->offsetSet($key, $value);
36
        }
37
    }
38
39
    /**
40
     * Sets a parameter or an object.
41
     *
42
     * Objects must be defined as Closures.
43
     *
44
     * Allowing any PHP callable leads to difficult to debug problems
45
     * as function names (strings) are callable (creating a function with
46
     * the same name as an existing parameter would break your container).
47
     *
48
     * @param string $id    The unique identifier for the parameter or object
49
     * @param mixed  $value The value of the parameter or a closure to define an object
50
     *
51
     * @throws \RuntimeException Prevent override of a frozen service
52
     */
53
    public function offsetSet($id, $value)
54
    {
55
        if (isset($this->frozen[$id])) {
56
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
57
        }
58
59
        $this->values[$id] = $value;
60
        $this->keys[$id] = true;
61
    }
62
63
    /**
64
     * Gets a parameter or an object.
65
     *
66
     * @param string $id The unique identifier for the parameter or object
67
     *
68
     * @return mixed The value of the parameter or an object
69
     *
70
     * @throws \InvalidArgumentException if the identifier is not defined
71
     */
72
    public function offsetGet($id)
73
    {
74
        if (!isset($this->keys[$id])) {
75
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
76
        }
77
78
        if (
79
            isset($this->raw[$id])
80
            || !is_object($this->values[$id])
81
            || isset($this->protected[$this->values[$id]])
82
            || !method_exists($this->values[$id], '__invoke')
83
        ) {
84
            return $this->values[$id];
85
        }
86
87
        if (isset($this->factories[$this->values[$id]])) {
88
            return $this->values[$id]($this);
89
        }
90
91
        $raw = $this->values[$id];
92
        $val = $this->values[$id] = $raw($this);
93
        $this->raw[$id] = $raw;
94
95
        $this->frozen[$id] = true;
96
97
        return $val;
98
    }
99
100
    /**
101
     * Checks if a parameter or an object is set.
102
     *
103
     * @param string $id The unique identifier for the parameter or object
104
     *
105
     * @return bool
106
     */
107
    public function offsetExists($id)
108
    {
109
        return isset($this->keys[$id]);
110
    }
111
112
    /**
113
     * Unsets a parameter or an object.
114
     *
115
     * @param string $id The unique identifier for the parameter or object
116
     */
117
    public function offsetUnset($id)
118
    {
119
        if (isset($this->keys[$id])) {
120
            if (is_object($this->values[$id])) {
121
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
122
            }
123
124
            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
125
        }
126
    }
127
128
    /**
129
     * Marks a callable as being a factory service.
130
     *
131
     * @param callable $callable A service definition to be used as a factory
132
     *
133
     * @return callable The passed callable
134
     *
135
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
136
     */
137 View Code Duplication
    public function factory($callable)
138
    {
139
        if (!method_exists($callable, '__invoke')) {
140
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
141
        }
142
143
        $this->factories->attach($callable);
144
145
        return $callable;
146
    }
147
148
    /**
149
     * Protects a callable from being interpreted as a service.
150
     *
151
     * This is useful when you want to store a callable as a parameter.
152
     *
153
     * @param callable $callable A callable to protect from being evaluated
154
     *
155
     * @return callable The passed callable
156
     *
157
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
158
     */
159 View Code Duplication
    public function protect($callable)
160
    {
161
        if (!method_exists($callable, '__invoke')) {
162
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
163
        }
164
165
        $this->protected->attach($callable);
166
167
        return $callable;
168
    }
169
170
    /**
171
     * Gets a parameter or the closure defining an object.
172
     *
173
     * @param string $id The unique identifier for the parameter or object
174
     *
175
     * @return mixed The value of the parameter or the closure defining an object
176
     *
177
     * @throws \InvalidArgumentException if the identifier is not defined
178
     */
179
    public function raw($id)
180
    {
181
        if (!isset($this->keys[$id])) {
182
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
183
        }
184
185
        if (isset($this->raw[$id])) {
186
            return $this->raw[$id];
187
        }
188
189
        return $this->values[$id];
190
    }
191
192
    /**
193
     * Extends an object definition.
194
     *
195
     * Useful when you want to extend an existing object definition,
196
     * without necessarily loading that object.
197
     *
198
     * @param string   $id       The unique identifier for the object
199
     * @param callable $callable A service definition to extend the original
200
     *
201
     * @return callable The wrapped callable
202
     *
203
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
204
     */
205
    public function extend($id, $callable)
206
    {
207
        if (!isset($this->keys[$id])) {
208
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
209
        }
210
211
        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
212
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
213
        }
214
215
        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
216
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
217
        }
218
219
        $factory = $this->values[$id];
220
221
        $extended = function ($c) use ($callable, $factory) {
222
            return $callable($factory($c), $c);
223
        };
224
225
        if (isset($this->factories[$factory])) {
226
            $this->factories->detach($factory);
227
            $this->factories->attach($extended);
228
        }
229
230
        return $this[$id] = $extended;
231
    }
232
233
    /**
234
     * Returns all defined value names.
235
     *
236
     * @return array An array of value names
237
     */
238
    public function keys()
239
    {
240
        return array_keys($this->values);
241
    }
242
}
243