Completed
Push — master ( e401e5...bd7189 )
by Julien
01:48
created

Container::getFromDelegate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 5
nc 3
nop 1
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\ArrayDefinition;
36
use Fwk\Di\Definitions\CallableDefinition;
37
use Fwk\Di\Definitions\ScalarDefinition;
38
use Fwk\Di\Events\AfterServiceLoadedEvent;
39
use Fwk\Di\Events\AfterServiceRegisteredEvent;
40
use Fwk\Di\Events\BeforeServiceLoadedEvent;
41
use Fwk\Di\Events\BeforeServiceRegisteredEvent;
42
use Fwk\Di\Exceptions\SearchException;
43
use Fwk\Events\Dispatcher;
44
use \ArrayAccess;
45
use Interop\Container\ContainerInterface;
46
use \SplObjectStorage;
47
48
/**
49
 * Container
50
 * 
51
 * THE Dependency Injection Container.
52
 *
53
 * @category Container
54
 * @package  Fwk\Di
55
 * @author   Julien Ballestracci <[email protected]>
56
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
57
 * @link     http://www.nitronet.org/fwk
58
 */
59
class Container extends Dispatcher implements ArrayAccess, ContainerInterface
60
{
61
    /**
62
     * The objects store
63
     * @var SplObjectStorage
64
     */
65
    protected $store = array();
66
67
    /**
68
     * Shared instances store
69
     * @var SplObjectStorage
70
     */
71
    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...
72
73
    /**
74
     * Container Properties
75
     * @var array
76
     */
77
    protected $properties = array();
78
79
    /**
80
     * Properties Keys (cached)
81
     * @var array
82
     */
83
    protected $propertiesMap = array();
84
85
    /**
86
     * Delegates Containers
87
     * @var SplObjectStorage
88
     */
89
    protected $delegates;
90
    
91
    /**
92
     * Constructor
93
     * 
94
     * @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...
95
     */
96
    public function __construct()
97
    {
98
        $this->store = new SplObjectStorage();
99
        $this->delegates = new SplObjectStorage();
100
        $this->_sharedInstances = new SplObjectStorage();
101
        $this->set('self', $this);
102
    }
103
    
104
    /**
105
     * Registers a definition
106
     * 
107
     * @param string  $name             Identifier
108
     * @param DefinitionInterface|mixed $definition Definition, callable or value
109
     *
110
     * @return Container
111
     */
112
    public function set($name, $definition)
113
    {
114
        if (!$definition instanceof DefinitionInterface) {
115
            if (is_callable($definition)) {
116
                $definition = CallableDefinition::factory($definition);
117
            } elseif (is_array($definition)) {
118
                $definition = ArrayDefinition::factory($definition);
119
            } else {
120
                $wasObj = is_object($definition);
121
                $definition = ScalarDefinition::factory($definition);
122
                if ($wasObj) {
123
                    $definition->setShared(true);
124
                    $this->_sharedInstances->attach($definition, $name);
125
                }
126
            }
127
        }
128
129
        $event = new BeforeServiceRegisteredEvent($this, $name, $definition);
130
        $this->notify($event);
131
132
        if ($event->isStopped()) {
133
            return $this;
134
        }
135
136
        $this->store->attach($definition, $name);
137
        $this->notify(new AfterServiceRegisteredEvent($this, $name, $definition));
138
139
        return $this;
140
    }
141
    
142
    /**
143
     * Load and returns a definition
144
     * 
145
     * @param string $name Identifier
146
     * 
147
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
148
     * @return mixed
149
     */
150
    public function get($name)
151
    {
152
        if ($name instanceof Reference) {
153
            $name = $name->getName();
154
        }
155
        
156
        if (!$this->has($name)) {
157
            return $this->getFromDelegate($name);
158
        }
159
160
        foreach ($this->store as $def) {
161
            if ($this->store->getInfo() === $name) {
162
                $definition = $def;
163
                break;
164
            }
165
        }
166
167
        /** @var DefinitionInterface $definition */
168
        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...
169
            foreach ($this->_sharedInstances as $inst) {
170
                if ($this->_sharedInstances->getInfo() === $name) {
171
                    return $inst;
172
                }
173
            }
174
        }
175
176
        $event      = new BeforeServiceLoadedEvent($this, $name, $definition);
177
        $this->notify($event);
178
179
        // the event has been stopped
180
        if ($event->isStopped()) {
181
            $return = $event->getReturnValue();
182
183
            if ($definition->isShared()) {
184
                $this->_sharedInstances->attach($return, $name);
185
            }
186
187
            return $return;
188
        }
189
190
        $return = $definition->invoke($this, $name);
191
        if ($definition->isShared()) {
192
            $this->_sharedInstances->attach($return, $name);
193
        }
194
195
        $afterEvent = new AfterServiceLoadedEvent($this, $name, $definition, $return);
196
        $this->notify($afterEvent);
197
198
        return $return;
199
    }
200
201
    /**
202
     * Loads properties from an INI file as definitions. 
203
     * Theses properties can then be referenced like @propName in other 
204
     * definitions.
205
     * 
206
     * @param string      $iniFile  Path/to/file.ini
207
     * @param null|string $category The INI category to be parsed
208
     * 
209
     * @return Container
210
     * @throws Exception
211
     */
212
    public function iniProperties($iniFile, $category = null)
213
    {
214
        if (!is_file($iniFile) || !is_readable($iniFile)) {
215
            throw new Exception('INI file not found/readable: '. $iniFile);
216
        }
217
        
218
        $props = parse_ini_file($iniFile, ($category !== null));
219
        if ($category !== null) {
220
            $props = (isset($props[$category]) ? $props[$category] : false);
221
        }
222
        
223
        if (!is_array($props)) {
224
            throw new Exception("No properties found in: $iniFile [$category]");
225
        }
226
        
227
        foreach ($props as $key => $prop) {
228
            $this->properties[$key] = str_replace(':packageDir', dirname($iniFile), $prop);
229
            $this->propertiesMap[$key] = ":". $key;
230
        }
231
        
232
        return $this;
233
    }
234
    
235
    /**
236
     * Returns a property (or $default if not defined) 
237
     * 
238
     * @param string $propName The property name
239
     * @param mixed  $default  Default value if the property is not defined
240
     * 
241
     * @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...
242
     */
243
    public function getProperty($propName, $default = null)
244
    {
245
        return (array_key_exists($propName, $this->properties) ? 
246
            $this->propertizeString($this->properties[$propName]) : 
247
            (is_string($default) ?
248
                $this->propertizeString($default) :
249
                $default
250
            )
251
        );
252
    }
253
    
254
    /**
255
     * Defines a property.
256
     * 
257
     * If the $value is null, the property will be unset.
258
     * 
259
     * It recommended to store only strings as property values. Register a
260
     * new Di definition for any other type.
261
     * 
262
     * @param string      $propName Property name
263
     * @param null|string $value    The prop value
264
     * 
265
     * @return Container
266
     */
267
    public function setProperty($propName, $value = null)
268
    {
269
        if (array_key_exists($propName, $this->properties) && $value === null) {
270
            unset($this->properties[$propName]);
271
            unset($this->propertiesMap[$propName]);
272
            return $this;
273
        }
274
        
275
        $this->properties[(string)$propName] = (string)$value;
276
        $this->propertiesMap[(string)$propName] = ":". (string)$propName;
277
        
278
        return $this;
279
    }
280
    
281
    
282
    /**
283
     * Transform properties references to their respective value
284
     * 
285
     * @param string $str String to be transformed
286
     * 
287
     * @return string
288
     */
289
    public function propertizeString($str)
290
    {
291
        return str_replace(
292
            array_values($this->propertiesMap),
293
            array_values($this->properties),
294
            $str
295
        );
296
    }
297
    
298
    /**
299
     * Unregisters a definition
300
     * 
301
     * @param string $name Identifier
302
     * 
303
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
304
     * @return boolean true on success
305
     */
306
    public function unregister($name)
307
    {
308
        if (!$this->has($name)) {
309
            throw new Exceptions\DefinitionNotFoundException($name);
310
        }
311
312
        $this->store->detach($this->getDefinition($name));
313
        foreach ($this->_sharedInstances as $inst => $defName) {
314
            if ($defName === $name) {
315
                $this->_sharedInstances->detach((object)$inst);
316
                break;
317
            }
318
        }
319
320
        return true;
321
    }
322
    
323
    /**
324
     * Tells if a definition exists at $offset
325
     * 
326
     * @param string $name Identifier
327
     * 
328
     * @return boolean
329
     */
330
    public function has($name)
331
    {
332
        foreach ($this->store as $def) {
333
            if ($this->store->getInfo() === $name) {
334
                return true;
335
            }
336
        }
337
338
        return false;
339
    }
340
341
    /**
342
     * Returns a Definition
343
     *
344
     * @param string $name Identifier
345
     *
346
     * @return DefinitionInterface
347
     * @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier
348
     */
349
    public function getDefinition($name)
350
    {
351
        foreach ($this->store as $def) {
352
            if ($this->store->getInfo() === $name) {
353
                return $def;
354
            }
355
        }
356
357
        throw new Exceptions\DefinitionNotFoundException($name);
358
    }
359
    
360
    /**
361
     * Tells if a definition is registered at $offset
362
     * 
363
     * @param string $offset Identifier
364
     * 
365
     * @return boolean
366
     */
367
    public function offsetExists($offset)
368
    {
369
        return $this->has($offset);
370
    }
371
    
372
    /**
373
     * Loads and returns a definition 
374
     * 
375
     * @param string $offset Identifier
376
     * 
377
     * @return mixed
378
     */
379
    public function offsetGet($offset)
380
    {
381
        return $this->get($offset);
382
    }
383
    
384
    /**
385
     * Registers a definition
386
     * 
387
     * @param string $offset Identifier
388
     * @param mixed  $value  Definition
389
     * 
390
     * @return Container
391
     */
392
    public function offsetSet($offset, $value)
393
    {
394
        return $this->set($offset, $value);
395
    }
396
    
397
    /**
398
     * Unregisters a Definition
399
     * 
400
     * @param string $offset Identifier
401
     * 
402
     * @return boolean
403
     */
404
    public function offsetUnset($offset)
405
    {
406
        return $this->unregister($offset);
407
    }
408
409
    /**
410
     * Adds a delegate/backup Container.
411
     *
412
     * @param ContainerInterface $container
413
     *
414
     * @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...
415
     */
416
    public function delegate(ContainerInterface $container)
417
    {
418
        if ($this->delegates->contains($container)) {
419
            return $this;
420
        }
421
422
        $this->delegates->attach($container);
423
424
        return $this;
425
    }
426
427
    /**
428
     * Tells if a service is in a delegated Container
429
     *
430
     * @param string $name
431
     *
432
     * @return boolean
433
     */
434
    public function hasInDelegate($name)
435
    {
436
        foreach ($this->delegates as $container) {
437
            /** @var ContainerInterface $container */
438
            if ($container->has($name)) {
439
                return true;
440
            }
441
        }
442
443
        return false;
444
    }
445
446
    /**
447
     * Loads a definition from the first delegated Container having in (FIFO)
448
     *
449
     * @param string $name Service identifier
450
     *
451
     * @throws Exceptions\DefinitionNotFoundException when the service is not found
452
     * @return mixed
453
     */
454
    public function getFromDelegate($name)
455
    {
456
        foreach ($this->delegates as $container) {
457
            /** @var ContainerInterface $container */
458
            if ($container->has($name)) {
459
                return $container->get($name);
460
            }
461
        }
462
463
        throw new Exceptions\DefinitionNotFoundException($name);
464
    }
465
466
    /**
467
     * Search definitions
468
     *
469
     * @param array $query Search query
470
     *
471
     * @return array<DefinitionInterface>
472
     */
473
    public function search(array $query)
474
    {
475
        $results = array();
476
        foreach ($this->store as $def) {
477
            $name = $this->store->getInfo();
478
            /** @var DefinitionInterface $def */
479
            if ($def->match($query, $this)) {
480
                $results[$name] = $def;
481
            }
482
        }
483
484
        return $results;
485
    }
486
}