Descriptor::has()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Fwk
4
 *
5
 * Copyright (c) 2011-2014, 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   Core
27
 * @package    Fwk\Core
28
 * @subpackage Components
29
 * @author     Julien Ballestracci <[email protected]>
30
 * @copyright  2011-2014 Julien Ballestracci <[email protected]>
31
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
32
 * @link       http://www.fwk.pw
33
 */
34
namespace Fwk\Core\Components\Descriptor;
35
36
use Fwk\Core\Application;
37
use Fwk\Di\Container;
38
use Fwk\Xml\Map;
39
use Fwk\Xml\XmlFile;
40
use Fwk\Xml\Path;
41
use Fwk\Di\ClassDefinition;
42
use Fwk\Core\Action\ProxyFactory;
43
use Fwk\Di\Xml\ContainerBuilder;
44
45
class Descriptor
46
{
47
    const DEFAULT_CATEGORY  = "fwk";
48
    
49
    protected $sources      = array();
50
    protected $properties   = array();
51
    protected $propertiesMap = array();
52
    protected $sourcesXml   = array();
53
    
54
    /**
55
     * Constructor
56
     * 
57
     * @param array $sources
58
     * @param array $properties 
59
     * 
60
     * @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...
61
     */
62 10
    public function __construct($sources, array $properties = array())
63
    {
64 10
        if (!is_array($sources)) {
65 10
            $sources = array($sources);
66 10
        }
67
        
68 10
        $this->sources      = $sources;
69 10
        $this->setAll($properties);
70 10
    }
71
    
72 2
    public function import($sourceFile)
73
    {
74 2
        $this->sources[] = $sourceFile;
75
        
76 2
        return $this;
77
    }
78
    
79 5
    public function iniProperties($iniFile, $category = null)
80
    {
81 5
        if (!is_file($iniFile) || !is_readable($iniFile)) {
82 1
            throw new Exception('INI file not found/readable: '. $iniFile);
83
        }
84
        
85 4
        if (null === $category) {
86 3
            $category = self::DEFAULT_CATEGORY;
87 3
        }
88
        
89 4
        $props = parse_ini_file($iniFile, true);
90 4
        if (!is_array($props) 
91 4
            || (!isset($props[$category]) || !is_array($props[$category]))
92 4
        ) {
93 1
            throw new Exception("No properties found in: $iniFile [$category]");
94
        }
95
        
96 3
        foreach ($props[$category] as $key => $value) {
97 3
            $this->properties[$key] = $this->propertizeString($value);
98 3
            $this->propertiesMap[$key] = ":". $key;
99 3
        }
100
        
101 3
        return $this;
102
    }
103
    
104
    /**
105
     *
106
     * @param string $propName
107
     * @param string $value
108
     * 
109
     * @return Descriptor 
110
     */
111 5
    public function set($propName, $value)
112
    {
113 5
        if (is_null($value)) {
114 4
            unset($this->properties[$propName]);
115 4
            unset($this->propertiesMap[$propName]);
116 4
        } else {
117 5
            $this->properties[$propName] = $value;
118 5
            $this->propertiesMap[$propName] = ":". $propName;
119
        }
120
        
121 5
        return $this;
122
    }
123
    
124
    /**
125
     *
126
     * @param type $propName
127
     * @param type $default
0 ignored issues
show
Documentation introduced by
Should the type for parameter $default not be type|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
128
     * @return type 
129
     */
130 2
    public function get($propName, $default = null)
131
    {
132 2
        return (array_key_exists($propName, $this->properties) ? 
133 2
            $this->properties[$propName] :
134
            $default
135 2
        );
136
    }
137
    
138
    /**
139
     *
140
     * @param string $propName
141
     * 
142
     * @return boolean
143
     */
144 2
    public function has($propName)
145
    {
146 2
        return array_key_exists($propName, $this->properties);
147
    }
148
    
149 10
    public function setAll(array $properties)
150
    {
151 10
        foreach ($this->properties as $key => $value) {
152
            $this->set($key, $value);
153 10
        }
154
155 10
        return $this;
156
    }
157
    
158
    /**
159
     * @return Application
160
     */
161 3
    public function execute($appName, Container $services = null)
162
    {
163 3
        $this->sources = array_reverse($this->sources);
164
        
165 3
        $app = Application::factory($appName, $services);
166 3
        if (null === $services) {
167 1
            $services = $app->getServices();
168 1
        }
169
        
170 3
        $app->addListener(new DescriptorListener($this));
171
        
172 3
        $this->loadIniFiles();
173 3
        $this->loadServices($services);
174
175
176 3
        foreach ($this->loadListeners($services) as $listener) {
177 3
            $app->addListener($listener);
178 3
        }
179
180 3
        foreach ($this->loadPlugins($services) as $plugin) {
181
            $app->plugin($plugin);
182 3
        }
183
184 3
        foreach ($this->loadActions() as $actionName => $str) {
185 3
            $app->register($actionName, ProxyFactory::factory($str));
186 3
        }
187
        
188 3
        return $app;
189
    }
190
    
191
    /**
192
     *
193
     * @param string $str
194
     * 
195
     * @return string
196
     */
197 7
    public function propertizeString($str)
198
    {
199 7
        return str_replace(
200 7
            array_values($this->propertiesMap),
201 7
            array_values($this->properties),
202
            $str
203 7
        );
204
    }
205
    
206 3
    public function loadServices(Container $container)
207
    {
208 3
        $xml        = array();
209 3
        $map        = $this->xmlServicesMapFactory();
210 3
        foreach ($this->sources as $source) {
211 3
            $parse  = $map->execute($this->getSourceXml($source));
212 3
            $res    = (isset($parse['services']) ? $parse['services'] : array());
213 3
            $xml[dirname($this->getSourceXml($source)->getRealPath())] = $res;
214 3
        }
215
        
216 3
        foreach ($xml as $baseDir => $data) {
217 3
            $this->set('baseDir', $baseDir);
218 3
            foreach ($data as $xmlFile => $infos) {
219
                $xmlMapClass = (!empty($infos['xmlMap']) ? 
220
                    $this->propertizeString($infos['xmlMap']) : 
221
                    null
222
                );
223
                
224
                if (null !== $xmlMapClass) {
225
                    $def = new ClassDefinition($xmlMapClass);
226
                    $mapObj = $def->invoke($container);
227
                } else {
228
                    $mapObj = null;
229
                }
230
                
231
                $builder = new ContainerBuilder($mapObj);
0 ignored issues
show
Bug introduced by
It seems like $mapObj defined by $def->invoke($container) on line 226 can also be of type object; however, Fwk\Di\Xml\ContainerBuilder::__construct() does only seem to accept null|object<Fwk\Xml\Map>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
232
                $builder->execute(
233
                    $this->propertizeString($xmlFile), 
234
                    $container
235
                );
236 3
            }
237 3
            $this->set('baseDir', null);
238 3
        }
239 3
    }
240
    
241 3
    protected function loadIniFiles()
242
    {
243 3
        $xml        = array();
244 3
        $map        = $this->xmlIniMapFactory();
245 3
        foreach ($this->sources as $source) {
246 3
            $parse  = $map->execute($this->getSourceXml($source));
247 3
            $res    = (isset($parse['ini']) ? $parse['ini'] : array());
248 3
            $xml[dirname($this->getSourceXml($source)->getRealPath())] = $res;
249 3
        }
250
        
251 3
        foreach ($xml as $baseDir => $data) {
252 3
            $this->set('baseDir', $baseDir);
253 3
            foreach ($data as $infos) {
254
                $cat = $this->propertizeString($infos['category']);
255
                $this->iniProperties(
256
                    $this->propertizeString($infos['value']), 
257
                    (empty($cat) ? null : $cat)
258
                );
259 3
            }
260 3
            $this->set('baseDir', null);
261 3
        }
262 3
    }
263
    
264 5
    public function loadListeners(Container $container)
265
    {
266 5
        $listeners  = array();
267 5
        $xml        = array();
268 5
        $map        = $this->xmlListenersMapFactory();
269 5
        foreach ($this->sources as $source) {
270 5
            $parse  = $map->execute($this->getSourceXml($source));
271 5
            $res    = (isset($parse['listeners']) ? $parse['listeners'] : array());
272 5
            $xml    = array_merge($xml, $res);
273 5
        }
274
        
275 5
        foreach ($xml as $data) {
276 5
            $finalParams = array();
277 5
            foreach ($data['params'] as $paramData) {
278 5
                $finalParams[$paramData['name']] = $paramData['value'];
279 5
            }
280
            
281 5
            if (isset($data['class']) && !empty($data['class'])) {
282 5
                $def = new ClassDefinition(
283 5
                    $this->propertizeString($data['class']), 
284
                    $finalParams
285 5
                );
286 5
                $listeners[] = $def->invoke($container);
287 5
            } elseif (isset($data['service']) && !empty($data['service'])) {
288
                $listeners[] = $container->get($data['service']);
289
            } else {
290
                throw new Exception('You must specify attribute "class" or "service" for listener');
291
            }
292 5
        }
293
        
294 5
        return $listeners;
295
    }
296
297 3
    public function loadPlugins(Container $container)
298
    {
299 3
        $plugins  = array();
300 3
        $xml        = array();
301 3
        $map        = $this->xmlPluginsMapFactory();
302 3
        foreach ($this->sources as $source) {
303 3
            $parse  = $map->execute($this->getSourceXml($source));
304 3
            $res    = (isset($parse['plugins']) ? $parse['plugins'] : array());
305 3
            $xml    = array_merge($xml, $res);
306 3
        }
307
308 3
        foreach ($xml as $data) {
309
            $finalParams = array();
310
            foreach ($data['params'] as $paramData) {
311
                $finalParams[$paramData['name']] = $paramData['value'];
312
            }
313
314
            if (isset($data['class']) && !empty($data['class'])) {
315
                $def = new ClassDefinition(
316
                    $this->propertizeString($data['class']),
317
                    $finalParams
318
                );
319
                $plugins[] = $def->invoke($container);
320
            } elseif (isset($data['service']) && !empty($data['service'])) {
321
                $plugins[] = $container->get($data['service']);
322
            } else {
323
                throw new Exception('You must specify attribute "class" or "service" for plugin');
324
            }
325 3
        }
326
327 3
        return $plugins;
328
    }
329
330 3
    public function loadActions()
331
    {
332 3
        $actions    = array();
333 3
        $xml        = array();
0 ignored issues
show
Unused Code introduced by
$xml is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
334 3
        $map        = $this->xmlActionMapFactory();
335 3
        foreach ($this->sources as $source) {
336 3
            $this->set('packageDir', dirname($this->getSourceXml($source)->getRealPath()));
337 3
            $parse  = $map->execute($this->getSourceXml($source));
338 3
            $res    = (isset($parse['actions']) ? $parse['actions'] : array());
339 3
            foreach ($res as $actionName => $data) {
340 3
                $actionName = $this->propertizeString($actionName);
341 3
                if (isset($data['class']) && isset($data['method'])) {
342 3
                    $actionStr = implode(':', array(
343 3
                        $this->propertizeString($data['class']), 
344 3
                        $this->propertizeString($data['method'])
345 3
                    ));
346 3
                } elseif (isset($data['shortcut'])) {
347 3
                    $str        = $data['shortcut'];
348 3
                    $actionStr  = $this->propertizeString($str);
349 3
                }
350
351 3
                $actions[$actionName] = $actionStr;
0 ignored issues
show
Bug introduced by
The variable $actionStr 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...
352 3
            }
353 3
        }
354
        
355 3
        $this->set('packageDir', null);
356
        
357 3
        return $actions;
358
    }
359
    
360
    /**
361
     *
362
     * 
363
     * @return array
364
     */
365 2
    public function getSourcesXml()
366
    {
367 2
        return $this->sourcesXml;
368
    }
369
    
370
    /**
371
     *
372
     * @param string $source
373
     * 
374
     * @return XmlFile
375
     */
376 5
    public function getSourceXml($source)
377
    {
378 5
        if (!isset($this->sourcesXml[$source])) {
379 5
            $this->sourcesXml[$source] = new XmlFile($source);
380 5
        }
381
        
382 5
        return $this->sourcesXml[$source];
383
    }
384
    
385
    /**
386
     * Builds an XML Map used to parse listeners from an XML source
387
     * 
388
     * @return Map
389
     */
390 5
    protected function xmlListenersMapFactory()
391
    {
392 5
        $map = new Map();
393 5
        $map->add(
394 5
            Path::factory('/fwk/listener', 'listeners')
395 5
            ->loop(true)
396 5
            ->attribute('class')
397 5
            ->attribute('service')
398 5
            ->addChildren(
399 5
                Path::factory('param', 'params')
400 5
                ->attribute('name')
401 5
                ->filter(array($this, 'propertizeString'))
0 ignored issues
show
Documentation introduced by
array($this, 'propertizeString') is of type array<integer,this<Fwk\C...riptor>","1":"string"}>, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
402 5
                ->value('value')
403 5
                ->loop(true)
404 5
             )
405 5
        );
406
        
407 5
        return $map;
408
    }
409
410
    /**
411
     * Builds an XML Map used to parse plugins from an XML source
412
     *
413
     * @return Map
414
     */
415 3
    protected function xmlPluginsMapFactory()
416
    {
417 3
        $map = new Map();
418 3
        $map->add(
419 3
            Path::factory('/fwk/plugin', 'plugins')
420 3
                ->loop(true)
421 3
                ->attribute('class')
422 3
                ->attribute('service')
423 3
                ->addChildren(
424 3
                    Path::factory('param', 'params')
425 3
                        ->attribute('name')
426 3
                        ->filter(array($this, 'propertizeString'))
0 ignored issues
show
Documentation introduced by
array($this, 'propertizeString') is of type array<integer,this<Fwk\C...riptor>","1":"string"}>, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
427 3
                        ->value('value')
428 3
                        ->loop(true)
429 3
                )
430 3
        );
431
432 3
        return $map;
433
    }
434
    
435
    /**
436
     * Builds an XML Map used to parse Actions from an XML source
437
     * 
438
     * @return Map
439
     */
440 3
    protected function xmlActionMapFactory()
441
    {
442 3
        $map = new Map();
443 3
        $map->add(
444 3
            Path::factory('/fwk/actions/action', 'actions')
445 3
            ->loop(true, '@name')
446 3
            ->attribute('class')
447 3
            ->attribute('method')
448 3
            ->attribute('shortcut')
449 3
        );
450
        
451 3
        return $map;
452
    }
453
    
454
    /**
455
     * Builds an XML Map used to parse .ini includes
456
     * 
457
     * @return Map
458
     */
459 3
    protected function xmlIniMapFactory()
460
    {
461 3
        $map = new Map();
462 3
        $map->add(
463 3
            Path::factory('/fwk/ini', 'ini', array())
464 3
            ->loop(true)
465 3
            ->attribute('category')
466 3
            ->value('value')
467 3
        );
468
        
469 3
        return $map;
470
    }
471
    
472
    /**
473
     * Builds an XML Map used to parse services includes (xml)
474
     * 
475
     * @return Map
476
     */
477 3
    protected function xmlServicesMapFactory()
478
    {
479 3
        $map = new Map();
480 3
        $map->add(
481 3
            Path::factory('/fwk/services', 'services', array())
482 3
            ->loop(true, '@xml')
483 3
            ->attribute('xmlMap')
484 3
        );
485
        
486 3
        return $map;
487
    }
488
}