Introspector   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 2
dl 0
loc 154
rs 9.8
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A introspect() 0 13 3
B scanPath() 0 24 6
D processClass() 0 40 10
C getClassNameInFile() 0 35 11
1
<?php
2
3
namespace Magium\Introspection;
4
5
use Magium\Actions\ActionInterface;
6
use Magium\Actions\ConfigurableActionInterface;
7
use Magium\Actions\OptionallyConfigurableActionInterface;
8
use Magium\Actions\StaticActionInterface;
9
use Magium\Assertions\AssertInterface;
10
use Magium\Assertions\AssertionInterface;
11
use Magium\Assertions\SelectorAssertionInterface;
12
use Magium\Commands\CommandInterface;
13
use Magium\Extractors\ExtractorInterface;
14
use Magium\Identities\IdentityInterface;
15
use Magium\Navigators\ConfigurableNavigatorInterface;
16
use Magium\Navigators\NavigatorInterface;
17
use Magium\Navigators\OptionallyConfigurableNavigatorInterface;
18
use Magium\Navigators\StaticNavigatorInterface;
19
use Magium\Themes\ThemeInterface;
20
use Zend\Log\LoggerInterface;
21
22
class Introspector
23
{
24
    protected $identifiers = [
25
        IdentityInterface::class,
26
        ExtractorInterface::class,
27
        CommandInterface::class,
28
        AssertInterface::class,
29
        ActionInterface::class,
30
        NavigatorInterface::class,
31
        ThemeInterface::class
32
    ];
33
34
    protected $typePreferences = [
35
        AssertInterface::class => [
36
            SelectorAssertionInterface::class,
37
            AssertionInterface::class
38
        ],
39
        ActionInterface::class => [
40
            ConfigurableActionInterface::class,
41
            OptionallyConfigurableActionInterface::class,
42
            StaticActionInterface::class
43
        ],
44
        NavigatorInterface::class => [
45
            ConfigurableNavigatorInterface::class,
46
            OptionallyConfigurableNavigatorInterface::class,
47
            StaticNavigatorInterface::class
48
        ]
49
    ];
50
51
    protected $logger;
52
53
    public function __construct(
54
        LoggerInterface $logger
55
    )
56
    {
57
        $this->logger = $logger;
58
    }
59
60
    public function introspect($paths)
61
    {
62
        if (!is_array($paths)) {
63
            $paths = [$paths];
64
        }
65
        $classes = [];
66
        foreach ($paths as $path) {
67
            $this->logger->info(sprintf('Examining path: %s', $path));
68
            $result = $this->scanPath($path);
69
            $classes = array_merge($classes, $result);
70
        }
71
        return $classes;
72
    }
73
74
    protected function scanPath($path)
75
    {
76
        $classes = [];
77
        $iterator = new \RecursiveIteratorIterator(
78
            new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS)
79
        );
80
        foreach ($iterator as $file) {
81
            /* @var $file \SplFileInfo */
82
            if ($file->isFile() && $file->getExtension() == 'php') {
83
                $filePath = $file->getRealPath();
84
                $className = $this->getClassNameInFile($filePath);
85
                if (!$className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
86
                    $this->logger->err(sprintf('Could not extract class from PHP file: %s', $filePath));
87
                } else {
88
                    $result = $this->processClass($className);
89
                    if ($result instanceof ComponentClass) {
90
                        $classes[$result->getClass()] = $result;
91
                        $this->logger->info(sprintf('Introspected Magium component class %s', $result->getClass()));
92
                    }
93
                }
94
            }
95
        }
96
        return $classes;
97
    }
98
99
    protected function processClass($class)
100
    {
101
        try {
102
            $reflectionClass = new \ReflectionClass($class);
103
        } catch (\Exception $e) {
104
            $this->logger->err($e->getMessage());
105
            return null;
106
        }
107
        if (!$reflectionClass->isInstantiable()) {
108
            return null;
109
        }
110
        foreach ($this->identifiers as $identifier) {
111
            if ($reflectionClass->implementsInterface($identifier)) {
112
                $name = $reflectionClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
113
                $baseType = $functionalType = $identifier;
114
                if (isset($this->typePreferences[$identifier])) {
115
                    foreach ($this->typePreferences[$identifier] as $preference) {
116
                        if ($reflectionClass->implementsInterface($preference)) {
117
                            $functionalType = $preference;
118
                            break;
119
                        }
120
                    }
121
                }
122
                $hierarchy = [];
123
                $rClass = $reflectionClass;
124
                while ($rClass && ($pClass = $rClass->getParentClass()) != false) {
125
                    $hierarchy[] = $pClass->getName();
126
                    $rClass = $pClass->getParentClass();
127
                }
128
                $hierarchy[] = $functionalType;
129
                $hierarchy[] = $baseType;
130
                $hierarchy = array_unique($hierarchy);
131
                $componentClass = new ComponentClass($name, $baseType, $functionalType, $hierarchy);
132
                $this->logger->debug('Extracted Magium component', ['object' => serialize($componentClass)]);
133
                return $componentClass;
134
            }
135
        }
136
        $this->logger->debug(sprintf('File %s was not a Magium component class', $class));
137
        return null;
138
    }
139
140
    protected function getClassNameInFile($file)
141
    {
142
        $namespace = $class = '';
0 ignored issues
show
Unused Code introduced by
$class 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...
143
        $content = file_get_contents($file);
144
        if (!$content) return null;
145
        $tokens = token_get_all($content);
146
        $count = count($tokens);
147
        for ($i = 2; $i < $count; $i++) {
148
            if ($tokens[$i - 2][0] == T_CLASS
149
                && $tokens[$i - 1][0] == T_WHITESPACE
150
                && $tokens[$i][0] == T_STRING
151
            ) {
152
153
                $class = $namespace . $tokens[$i][1];
154
                $this->logger->debug(sprintf('Extracted %s from %s', $class, $file));
155
                return $class;
156
            } else if ($tokens[$i - 2][0] == T_NAMESPACE
157
                && $tokens[$i - 1][0] == T_WHITESPACE
158
                && $tokens[$i][0] == T_STRING) {
159
160
                $plus = 0;
161
                while ($tokens[$i+$plus] != ';') {
162
                    $token = $tokens[$i+$plus];
163
                    if ($token[0] == T_STRING) {
164
                        $namespace .= $token[1] . '\\';
165
                    }
166
                    $plus++;
167
                }
168
169
                $this->logger->debug(sprintf('Extracted namespace %s from %s', $namespace, $file));
170
            }
171
        }
172
        return null;
173
174
    }
175
}
176