Completed
Push — feat-events-tags ( 26067f...836389 )
by Julien
02:24
created

Container::search()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.4285
cc 3
eloc 7
nc 3
nop 1
crap 12
1
<?php
2
/**
3
 * Fwk
4
 *
5
 * Copyright (c) 2011-2012, Julien Ballestracci <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
15
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
21
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
 * POSSIBILITY OF SUCH DAMAGE.
23
 *
24
 * PHP Version 5.3
25
 *
26
 * @category  DependencyInjection
27
 * @package   Fwk\Di
28
 * @author    Julien Ballestracci <[email protected]>
29
 * @copyright 2011-2014 Julien Ballestracci <[email protected]>
30
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
31
 * @link      http://www.nitronet.org/fwk
32
 */
33
namespace Fwk\Di;
34
35
use Fwk\Di\Definitions\CallableDefinition;
36
use Fwk\Di\Definitions\ScalarDefinition;
37
use Fwk\Di\Events\AfterServiceLoadedEvent;
38
use Fwk\Di\Events\AfterServiceRegisteredEvent;
39
use Fwk\Di\Events\BeforeServiceLoadedEvent;
40
use Fwk\Di\Events\BeforeServiceRegisteredEvent;
41
use Fwk\Di\Exceptions\SearchException;
42
use Fwk\Events\Dispatcher;
43
use \ArrayAccess;
44
use Interop\Container\ContainerInterface;
45
use \SplObjectStorage;
46
47
/**
48
 * Container
49
 * 
50
 * THE Dependency Injection Container.
51
 *
52
 * @category Container
53
 * @package  Fwk\Di
54
 * @author   Julien Ballestracci <[email protected]>
55
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
56
 * @link     http://www.nitronet.org/fwk
57
 */
58
class Container extends Dispatcher implements ArrayAccess, ContainerInterface
59
{
60
    /**
61
     * The objects store
62
     * @var SplObjectStorage
63
     */
64
    protected $store = array();
65
66
    /**
67
     * Shared instances store
68
     * @var SplObjectStorage
69
     */
70
    private $_sharedInstances;
0 ignored issues
show
Coding Style introduced by
$_sharedInstances does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
71
72
    /**
73
     * Container Properties
74
     * @var array
75
     */
76
    protected $properties = array();
77
78
    /**
79
     * Properties Keys (cached)
80
     * @var array
81
     */
82
    protected $propertiesMap = array();
83
84
    /**
85
     * Delegates Containers
86
     * @var SplObjectStorage
87
     */
88
    protected $delegates;
89
    
90
    /**
91
     * Constructor
92
     * 
93 40
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
94
     */
95 40
    public function __construct()
96 40
    {
97 40
        $this->store = new SplObjectStorage();
98
        $this->delegates = new SplObjectStorage();
99
        $this->_sharedInstances = new SplObjectStorage();
100
        $this->set('self', $this);
101
    }
102
    
103
    /**
104
     * Registers a definition
105
     * 
106
     * @param string  $name             Identifier
107
     * @param DefinitionInterface|mixed $definition Definition, callable or value
108
     *
109 40
     * @return Container
110
     */
111
    public function set($name, $definition)
112 40
    {
113 40
        if (!$definition instanceof DefinitionInterface) {
114
            if (is_callable($definition)) {
115 40
                $definition = CallableDefinition::factory($definition);
116
            } else {
117
                $wasObj = is_object($definition);
118
                $definition = ScalarDefinition::factory($definition);
119 40
                if ($wasObj) {
120 40
                    $definition->setShared(true);
121 40
                    $this->_sharedInstances->attach($definition, $name);
122
                }
123 40
            }
124
        }
125 40
126
        $event = new BeforeServiceRegisteredEvent($this, $name, $definition);
127
        $this->notify($event);
128
129
        if ($event->isStopped()) {
130
            return $this;
131
        }
132
133
        $this->store->attach($definition, $name);
134
        $this->notify(new AfterServiceRegisteredEvent($this, $name, $definition));
135
136 26
        return $this;
137
    }
138 26
    
139
    /**
140
     * Load and returns a definition
141
     * 
142 26
     * @param string $name Identifier
143 6
     * 
144
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
145
     * @return mixed
146 20
     */
147 20
    public function get($name)
148 20
    {
149 20
        if ($name instanceof Reference) {
150 4
            $name = $name->getName();
151
        }
152
        
153 20
        if (!$this->has($name)) {
154 20
            return $this->getFromDelegate($name);
155
        }
156 20
157
        foreach ($this->store as $def) {
158
            if ($this->store->getInfo() === $name) {
159 20
                $definition = $def;
160 1
                break;
161
            }
162 1
        }
163
164
        /** @var DefinitionInterface $definition */
165
        if ($definition->isShared()) {
0 ignored issues
show
Bug introduced by
The variable $definition does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
166 1
            foreach ($this->_sharedInstances as $inst) {
167
                if ($this->_sharedInstances->getInfo() === $name) {
168
                    return $inst;
169 19
                }
170
            }
171 19
        }
172 7
173 19
        $event      = new BeforeServiceLoadedEvent($this, $name, $definition);
174 7
        $this->notify($event);
175 7
176 6
        // the event has been stopped
177
        if ($event->isStopped()) {
178
            $return = $event->getReturnValue();
179 19
180 4
            if ($definition->isShared()) {
181 4
                $this->_sharedInstances->attach($return, $name);
182
            }
183 19
184 19
            return $return;
185
        }
186 19
187
        $return = $definition->invoke($this, $name);
188 19
        if ($definition->isShared()) {
189
            $this->_sharedInstances->attach($return, $name);
190
        }
191
192
        $afterEvent = new AfterServiceLoadedEvent($this, $name, $definition, $return);
193
        $this->notify($afterEvent);
194
195
        return $return;
196
    }
197
198 1
    /**
199
     * Loads properties from an INI file as definitions. 
200 1
     * Theses properties can then be referenced like @propName in other 
201
     * definitions.
202
     * 
203
     * @param string      $iniFile  Path/to/file.ini
204 1
     * @param null|string $category The INI category to be parsed
205 1
     * 
206 1
     * @return Container
207 1
     * @throws Exception
208
     */
209 1
    public function iniProperties($iniFile, $category = null)
210
    {
211
        if (!is_file($iniFile) || !is_readable($iniFile)) {
212
            throw new Exception('INI file not found/readable: '. $iniFile);
213
        }
214
        
215
        $props = parse_ini_file($iniFile, ($category !== null));
216
        if ($category !== null) {
217
            $props = (isset($props[$category]) ? $props[$category] : false);
218
        }
219
        
220
        if (!is_array($props)) {
221
            throw new Exception("No properties found in: $iniFile [$category]");
222
        }
223 5
        
224
        foreach ($props as $key => $prop) {
225 5
            $this->properties[$key] = str_replace(':packageDir', dirname($iniFile), $prop);
226
            $this->propertiesMap[$key] = ":". $key;
227
        }
228
        
229 5
        return $this;
230 5
    }
231
    
232
    /**
233
     * Returns a property (or $default if not defined) 
234 5
     * 
235
     * @param string $propName The property name
236
     * @param mixed  $default  Default value if the property is not defined
237
     * 
238 5
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use object|integer|double|null|array|boolean|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
239 5
     */
240 5
    public function getProperty($propName, $default = null)
241 5
    {
242
        return (array_key_exists($propName, $this->properties) ? 
243 5
            $this->propertizeString($this->properties[$propName]) : 
244
            (is_string($default) ?
245
                $this->propertizeString($default) :
246
                $default
247
            )
248
        );
249
    }
250
    
251
    /**
252
     * Defines a property.
253
     * 
254 3
     * If the $value is null, the property will be unset.
255
     * 
256 3
     * It recommended to store only strings as property values. Register a
257 3
     * new Di definition for any other type.
258 1
     * 
259 1
     * @param string      $propName Property name
260
     * @param null|string $value    The prop value
261
     * 
262 3
     * @return Container
263
     */
264
    public function setProperty($propName, $value = null)
265
    {
266
        if (array_key_exists($propName, $this->properties) && $value === null) {
267
            unset($this->properties[$propName]);
268
            unset($this->propertiesMap[$propName]);
269
            return $this;
270
        }
271
        
272
        $this->properties[(string)$propName] = (string)$value;
273
        $this->propertiesMap[(string)$propName] = ":". (string)$propName;
274
        
275
        return $this;
276
    }
277
    
278 7
    
279
    /**
280 7
     * Transform properties references to their respective value
281
     * 
282
     * @param string $str String to be transformed
283
     * 
284
     * @return string
285
     */
286 7
    public function propertizeString($str)
287 7
    {
288
        return str_replace(
289 7
            array_values($this->propertiesMap),
290
            array_values($this->properties),
291
            $str
292
        );
293
    }
294
    
295
    /**
296
     * Unregisters a definition
297
     * 
298
     * @param string $name Identifier
299
     * 
300 21
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
301
     * @return boolean true on success
302 21
     */
303 21
    public function unregister($name)
304 21
    {
305
        if (!$this->has($name)) {
306 21
            throw new Exceptions\DefinitionNotFoundException($name);
307
        }
308
309
        $this->store->detach($this->getDefinition($name));
310
        foreach ($this->_sharedInstances as $inst => $defName) {
311
            if ($defName === $name) {
312
                $this->_sharedInstances->detach((object)$inst);
313
                break;
314
            }
315
        }
316
317 3
        return true;
318
    }
319 3
    
320 1
    /**
321
     * Tells if a definition exists at $offset
322
     * 
323 2
     * @param string $name Identifier
324 2
     * 
325
     * @return boolean
326 2
     */
327
    public function has($name)
328
    {
329
        foreach ($this->store as $def) {
330
            if ($this->store->getInfo() === $name) {
331
                return true;
332
            }
333
        }
334
335
        return false;
336
    }
337 9
338
    /**
339 9
     * Returns a Definition
340 1
     *
341
     * @param string $name Identifier
342
     *
343 8
     * @return DefinitionInterface
344
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
345 8
     */
346
    public function getDefinition($name)
347
    {
348
        foreach ($this->store as $def) {
349
            if ($this->store->getInfo() === $name) {
350
                return $def;
351
            }
352
        }
353
354
        throw new Exceptions\DefinitionNotFoundException($name);
355 29
    }
356
    
357 29
    /**
358
     * Tells if a definition is registered at $offset
359
     * 
360
     * @param string $offset Identifier
361
     * 
362
     * @return boolean
363
     */
364
    public function offsetExists($offset)
365
    {
366
        return $this->has($offset);
367
    }
368
    
369
    /**
370
     * Loads and returns a definition 
371
     * 
372
     * @param string $offset Identifier
373
     * 
374
     * @return mixed
375
     */
376
    public function offsetGet($offset)
377
    {
378
        return $this->get($offset);
379
    }
380
    
381
    /**
382
     * Registers a definition
383
     * 
384
     * @param string $offset Identifier
385
     * @param mixed  $value  Definition
386
     * 
387
     * @return Container
388
     */
389
    public function offsetSet($offset, $value)
390
    {
391
        return $this->set($offset, $value);
392 16
    }
393
    
394 16
    /**
395
     * Unregisters a Definition
396
     * 
397
     * @param string $offset Identifier
398
     * 
399
     * @return boolean
400
     */
401
    public function offsetUnset($offset)
402
    {
403
        return $this->unregister($offset);
404
    }
405
406
    /**
407
     * Adds a delegate/backup Container.
408
     *
409
     * @param ContainerInterface $container
410
     *
411
     * @return ContainerInterface
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Container.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
412
     */
413
    public function delegate(ContainerInterface $container)
414
    {
415
        if ($this->delegates->contains($container)) {
416
            return $this;
417
        }
418
419
        $this->delegates->attach($container);
420
421
        return $this;
422
    }
423
424
    /**
425
     * Tells if a service is in a delegated Container
426
     *
427
     * @param string $name
428
     *
429
     * @return boolean
430
     */
431
    public function hasInDelegate($name)
432
    {
433
        foreach ($this->delegates as $container) {
434
            /** @var ContainerInterface $container */
435
            if ($container->has($name)) {
436
                return true;
437
            }
438
        }
439
440
        return false;
441
    }
442
443
    /**
444
     * Loads a definition from the first delegated Container having in (FIFO)
445
     *
446
     * @param string $name Service identifier
447
     *
448
     * @throws Exceptions\DefinitionNotFoundException when the service is not found
449
     * @return mixed
450
     */
451
    public function getFromDelegate($name)
452
    {
453
        foreach ($this->delegates as $container) {
454 6
            /** @var ContainerInterface $container */
455
            if ($container->has($name)) {
456 6
                return $container->get($name);
457
            }
458
        }
459
460
        throw new Exceptions\DefinitionNotFoundException($name);
461 6
    }
462
463 6
    /**
464
     * Search definitions
465
     *
466
     * @param array $query Search query
467
     *
468
     * @return array<DefinitionInterface>
469
     */
470
    public function search(array $query)
471
    {
472
        $results = array();
473
        foreach ($this->store as $def) {
474
            $name = $this->store->getInfo();
475
            /** @var DefinitionInterface $def */
476
            if ($def->match($query, $this)) {
477
                $results[$name] = $def;
478
            }
479
        }
480
481
        return $results;
482
    }
483
}