Passed
Branch trunk (3f392c)
by SuperNova.WS
04:59
created

Container::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of Pimple.
5
 *
6
 * Copyright (c) 2009 Fabien Potencier
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is furnished
13
 * to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in all
16
 * copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
namespace Pimple;
28
29
/**
30
 * Container main class.
31
 *
32
 * @author  Fabien Potencier
33
 */
34
class Container implements \ArrayAccess
35
{
36
    private $values = array();
37
    private $factories;
38
    private $protected;
39
    private $frozen = array();
40
    private $raw = array();
41
    private $keys = array();
42
43
    /**
44
     * Instantiate the container.
45
     *
46
     * Objects and parameters can be passed as argument to the constructor.
47
     *
48
     * @param array $values The parameters or objects.
49
     */
50 40
    public function __construct(array $values = array())
51
    {
52 40
        $this->factories = new \SplObjectStorage();
53 40
        $this->protected = new \SplObjectStorage();
54
55 40
        foreach ($values as $key => $value) {
56 1
            $this->offsetSet($key, $value);
57 40
        }
58 40
    }
59
60
    /**
61
     * Sets a parameter or an object.
62
     *
63
     * Objects must be defined as Closures.
64
     *
65
     * Allowing any PHP callable leads to difficult to debug problems
66
     * as function names (strings) are callable (creating a function with
67
     * the same name as an existing parameter would break your container).
68
     *
69
     * @param string $id    The unique identifier for the parameter or object
70
     * @param mixed  $value The value of the parameter or a closure to define an object
71
     *
72
     * @throws \RuntimeException Prevent override of a frozen service
73
     */
74 32
    public function offsetSet($id, $value)
75
    {
76 32
        if (isset($this->frozen[$id])) {
77 1
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
78
        }
79
80 32
        $this->values[$id] = $value;
81 32
        $this->keys[$id] = true;
82 32
    }
83
84
    /**
85
     * Gets a parameter or an object.
86
     *
87
     * @param string $id The unique identifier for the parameter or object
88
     *
89
     * @return mixed The value of the parameter or an object
90
     *
91
     * @throws \InvalidArgumentException if the identifier is not defined
92
     */
93 23
    public function offsetGet($id)
94
    {
95 23
        if (!isset($this->keys[$id])) {
96 1
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
97
        }
98
99
        if (
100 22
            isset($this->raw[$id])
101 22
            || !is_object($this->values[$id])
102 22
            || isset($this->protected[$this->values[$id]])
103 18
            || !method_exists($this->values[$id], '__invoke')
104 22
        ) {
105 13
            return $this->values[$id];
106
        }
107
108 15
        if (isset($this->factories[$this->values[$id]])) {
109 5
            return $this->values[$id]($this);
110
        }
111
112 14
        $raw = $this->values[$id];
113 14
        $val = $this->values[$id] = $raw($this);
114 14
        $this->raw[$id] = $raw;
115
116 14
        $this->frozen[$id] = true;
117
118 14
        return $val;
119
    }
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 2
    public function offsetExists($id)
129
    {
130 2
        return isset($this->keys[$id]);
131
    }
132
133
    /**
134
     * Unsets a parameter or an object.
135
     *
136
     * @param string $id The unique identifier for the parameter or object
137
     */
138 3
    public function offsetUnset($id)
139
    {
140 3
        if (isset($this->keys[$id])) {
141 3
            if (is_object($this->values[$id])) {
142 2
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
143 2
            }
144
145 3
            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
146 3
        }
147 3
    }
148
149
    /**
150
     * Marks a callable as being a factory service.
151
     *
152
     * @param callable $callable A service definition to be used as a factory
153
     *
154
     * @return callable The passed callable
155
     *
156
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
157
     */
158 9 View Code Duplication
    public function factory($callable)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
    {
160 9
        if (!method_exists($callable, '__invoke')) {
161 2
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
162
        }
163
164 7
        $this->factories->attach($callable);
165
166 7
        return $callable;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $callable returns the type object which is incompatible with the documented return type callable.
Loading history...
167
    }
168
169
    /**
170
     * Protects a callable from being interpreted as a service.
171
     *
172
     * This is useful when you want to store a callable as a parameter.
173
     *
174
     * @param callable $callable A callable to protect from being evaluated
175
     *
176
     * @return callable The passed callable
177
     *
178
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
179
     */
180 4 View Code Duplication
    public function protect($callable)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
    {
182 4
        if (!method_exists($callable, '__invoke')) {
183 2
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
184
        }
185
186 2
        $this->protected->attach($callable);
187
188 2
        return $callable;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $callable returns the type object which is incompatible with the documented return type callable.
Loading history...
189
    }
190
191
    /**
192
     * Gets a parameter or the closure defining an object.
193
     *
194
     * @param string $id The unique identifier for the parameter or object
195
     *
196
     * @return mixed The value of the parameter or the closure defining an object
197
     *
198
     * @throws \InvalidArgumentException if the identifier is not defined
199
     */
200 3
    public function raw($id)
201
    {
202 3
        if (!isset($this->keys[$id])) {
203 1
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
204
        }
205
206 2
        if (isset($this->raw[$id])) {
207
            return $this->raw[$id];
208
        }
209
210 2
        return $this->values[$id];
211
    }
212
213
    /**
214
     * Extends an object definition.
215
     *
216
     * Useful when you want to extend an existing object definition,
217
     * without necessarily loading that object.
218
     *
219
     * @param string   $id       The unique identifier for the object
220
     * @param callable $callable A service definition to extend the original
221
     *
222
     * @return callable The wrapped callable
223
     *
224
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
225
     */
226 10
    public function extend($id, $callable)
227
    {
228 10
        if (!isset($this->keys[$id])) {
229 1
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
230
        }
231
232 9
        if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) {
233 2
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
234
        }
235
236 7
        if (!is_object($callable) || !method_exists($callable, '__invoke')) {
237 2
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
238
        }
239
240 5
        $factory = $this->values[$id];
241
242 5
        $extended = function ($c) use ($callable, $factory) {
243 4
            return $callable($factory($c), $c);
244 5
        };
245
246 5
        if (isset($this->factories[$factory])) {
247 3
            $this->factories->detach($factory);
248 3
            $this->factories->attach($extended);
0 ignored issues
show
Bug introduced by
$extended of type callable is incompatible with the type object expected by parameter $object of SplObjectStorage::attach(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

248
            $this->factories->attach(/** @scrutinizer ignore-type */ $extended);
Loading history...
249 3
        }
250
251 5
        return $this[$id] = $extended;
252
    }
253
254
    /**
255
     * Returns all defined value names.
256
     *
257
     * @return array An array of value names
258
     */
259 1
    public function keys()
260
    {
261 1
        return array_keys($this->values);
262
    }
263
264
    /**
265
     * Registers a service provider.
266
     *
267
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
268
     * @param array                    $values   An array of values that customizes the provider
269
     *
270
     * @return static
271
     */
272 2
    public function register(ServiceProviderInterface $provider, array $values = array())
273
    {
274 2
        $provider->register($this);
275
276 2
        foreach ($values as $key => $value) {
277 1
            $this[$key] = $value;
278 2
        }
279
280 2
        return $this;
281
    }
282
}
283