Completed
Push — master ( 9971b9...3d6cb8 )
by Richard
06:14
created

Indexer   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 343
Duplicated Lines 6.71 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 79.73%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 23
loc 343
wmc 40
lcom 1
cbo 11
ccs 118
cts 148
cp 0.7973
rs 8.2608

20 Methods

Rating   Name   Duplication   Size   Complexity  
A init_flightcontrol() 0 5 1
A __construct() 0 17 1
B index_directory() 0 22 4
A index_file() 0 11 2
A index_content() 0 17 2
A on_enter_definition() 0 4 1
A on_leave_definition() 0 4 1
A on_enter_misc() 0 4 1
A on_leave_misc() 0 4 1
A on_enter_or_leave_something() 0 15 4
A call_misc_listener() 12 12 4
A call_definition_listener() 11 11 4
A file_path() 0 3 1
A in_entities() 0 3 1
A beforeTraverse() 0 5 1
A afterTraverse() 0 5 1
A enterNode() 0 20 2
A get_type_of() 0 14 4
A is_definition() 0 5 3
A leaveNode() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Indexer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Indexer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the license along with the code.
9
 */
10
11
namespace Lechimp\Dicto\Indexer;
12
13
use Lechimp\Dicto\Variables\Variable;
14
use PhpParser\Node as N;
15
use Psr\Log\LoggerInterface as Log;
16
use League\Flysystem\Adapter\Local;
17
use League\Flysystem\Filesystem;
18
use Lechimp\Flightcontrol\Flightcontrol;
19
use Lechimp\Flightcontrol\File;
20
use Lechimp\Flightcontrol\FSObject;
21
22
/**
23
 * Creates an index of source files.
24
 */
25
class Indexer implements Location, ListenerRegistry, \PhpParser\NodeVisitor {
26
    /**
27
     * @var Log
28
     */
29
    protected $log;
30
31
    /**
32
     * @var Insert
33
     */
34
    protected $insert;
35
36
    /**
37
     * @var \PhpParser\Parser
38
     */
39
    protected $parser;
40
41
    /**
42
     * @var array   string => array()
43
     */
44
    protected $listeners_enter_definition;
45
46
    /**
47
     * @var array   string => array()
48
     */
49
    protected $listeners_leave_definition;
50
51
    /**
52
     * @var array   string => array()
53
     */
54
    protected $listeners_enter_misc;
55
56
    /**
57
     * @var array   string => array()
58
     */
59
    protected $listeners_leave_misc;
60
61
    // state for parsing a file
62
63
    /**
64
     * @var string|null
65
     */
66
    protected $file_path = null;
67
68
    /**
69
     * @var string|null
70
     */
71
    protected $file_content = null;
72
73
    /**
74
     * This contains the stack of ids were currently in, i.e. the nesting of
75
     * known code blocks we are in.
76
     *
77
     * @var array|null  contain ($definition_type, $definition_id)
78
     */
79
    protected $definition_stack = null;
80
81 10
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
82 10
        $this->log = $log;
83 10
        $this->parser = $parser;
84 10
        $this->insert = $insert;
85 10
        $this->listeners_enter_definition = array
86 10
            ( 0 => array()
87 10
            );
88 10
        $this->listeners_leave_definition = array
89 10
            ( 0 => array()
90 10
            );
91 10
        $this->listeners_enter_misc = array
92 10
            ( 0 => array()
93 10
            );
94 10
        $this->listeners_leave_misc = array
95 10
            ( 0 => array()
96 10
            );
97 10
    }
98
99
    /**
100
     * Index a directory.
101
     *
102
     * @param   string  $path
103
     * @param   array   $ignore_paths
104
     * @return  null
105
     */
106 3
    public function index_directory($path, array $ignore_paths) {
107 3
        $fc = $this->init_flightcontrol($path);
108 3
        $fc->directory("/")
109 3
            ->recurseOn()
110
            ->filter(function(FSObject $obj) use (&$ignore_paths) {
111 3
                foreach ($ignore_paths as $pattern) {
112 3
                    if (preg_match("%$pattern%", $obj->path()) !== 0) {
113 3
                        return false;
114
                    }
115 3
                }
116 3
                return true;
117 3
            })
118 3
            ->foldFiles(null, function($_, File $file) use ($path) {
119
                try {
120 3
                    $this->index_file($path, $file->path());
121
                }
122 3
                catch (\PhpParser\Error $e) {
123
                    $this->log->error("in ".$file->path().": ".$e->getMessage());
124
                }
125 3
            });
126
127 3
    }
128
129
    /**
130
     * Initialize the filesystem abstraction.
131
     *
132
     * @return  Flightcontrol
133
     */
134 3
    public function init_flightcontrol($path) {
135 3
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
136 3
        $flysystem = new Filesystem($adapter);
137 3
        return new Flightcontrol($flysystem);
138
    }
139
140
    /**
141
     * @param   string  $base_dir
142
     * @param   string  $path
143
     * @return  null
144
     */
145
    public function index_file($base_dir, $path) {
146
        assert('is_string($base_dir)');
147
        assert('is_string($path)');
148
        $this->log->info("indexing: ".$path);
149
        $full_path = "$base_dir/$path";
150
        $content = file_get_contents($full_path);
151
        if ($content === false) {
152
            throw \InvalidArgumentException("Can't read file $path.");
153
        }
154
        $this->index_content($path, $content);
155
    }
156
157
    /**
158
     * @param   string  $path
159
     * @param   string  $content
160
     * @return  null
161
     */
162 10
    public function index_content($path, $content) {
163 10
        assert('is_string($path)');
164 10
        assert('is_string($content)');
165
166 10
        $stmts = $this->parser->parse($content);
167 10
        if ($stmts === null) {
168
            throw new \RuntimeException("Can't parse file $path.");
169
        }
170
171 10
        $traverser = new \PhpParser\NodeTraverser;
172 10
        $traverser->addVisitor($this);
173
174 10
        $this->definition_stack = array();
175 10
        $this->file_path = $path;
176 10
        $this->file_content = $content;
177 10
        $traverser->traverse($stmts);
178 10
    }
179
180
   // from ListenerRegistry 
181
182
    /**
183
     * @inheritdoc
184
     */
185
    public function on_enter_definition($types, \Closure $listener) {
186
        $this->on_enter_or_leave_something("listeners_enter_definition", $types, $listener);
187
        return $this;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193
    public function on_leave_definition($types, \Closure $listener) {
194
        $this->on_enter_or_leave_something("listeners_leave_definition", $types, $listener);
195
        return $this;
196
    }
197
198
    /**
199
     * @inheritdoc
200
     */
201 10
    public function on_enter_misc($classes, \Closure $listener) {
202 10
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
203 10
        return $this;
204
    }
205
206
    /**
207
     * @inheritdoc
208
     */
209
    public function on_leave_misc($classes, \Closure $listener) {
210
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
211
        return $this;
212
    }
213
214
    // generalizes over over on_enter/leave_xx
215
216
    /**
217
     * @param   string      $what
218
     * @param   array|null  $things
219
     */
220 10
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
221 10
        $loc = &$this->$what;
222 10
        if ($things === null) {
223
            $loc[0][] = $listener;
224
        }
225
        else {
226 10
            foreach ($things as $thing) {
227 10
                assert('is_string($thing)');
228 10
                if (!array_key_exists($thing, $loc)) {
229 10
                    $loc[$thing] = array();
230 10
                }
231 10
                $loc[$thing][] = $listener;
232 10
            }
233
        }
234 10
    }
235
236
    // generalizes over calls to misc listeners
237
238
    /**
239
     * @param   string          $which
240
     * @param   \PhpParser\Node $node
241
     */
242 7 View Code Duplication
    protected function call_misc_listener($which, \PhpParser\Node $node) {
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...
243 7
        $listeners = &$this->$which;
244 7
        foreach ($listeners[0] as $listener) {
245
            $listener($this->insert, $this, $node);
246 7
        }
247 7
        $cls = get_class($node);
248 7
        if (array_key_exists($cls, $listeners)) {
249 6
            foreach ($listeners[$cls] as $listener) {
250 6
                $listener($this->insert, $this, $node);
251 6
            }
252 6
        }
253 7
    }
254
255
    /**
256
     * @param   string                  $which
257
     * @param   string                  $type
258
     * @param   int                     $type
259
     * @param   \PhpParser\Node|null    $node
260
     */
261 9 View Code Duplication
    protected function call_definition_listener($which, $type, $id, \PhpParser\Node $node = null) {
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...
262 9
        $listeners = &$this->$which;
263 9
        foreach ($listeners[0] as $listener) {
264
            $listener($this->insert, $this, $type, $id, $node);
265 9
        }
266 9
        if (array_key_exists($type, $listeners)) {
267
            foreach ($listeners[$type] as $listener) {
268
                $listener($this->insert, $this, $type, $id, $node);
269
            }
270
        }
271 9
    }
272
273
   // from Location
274
275
    /**
276
     * @inheritdoc
277
     */
278 6
    public function file_path() {
279 6
        return $this->file_path;
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285 6
    public function in_entities() {
286 6
        return $this->definition_stack;
287
    }
288
289
    // from \PhpParser\NodeVisitor
290
291
    /**
292
     * @inheritdoc
293
     */
294 10
    public function beforeTraverse(array $nodes) {
295 10
        $id = $this->insert->source($this->file_path, $this->file_content);
0 ignored issues
show
Unused Code introduced by
$id 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...
296
297 10
        return null;
298
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303 10
    public function afterTraverse(array $nodes) {
304 10
        list($type, $id) = array_pop($this->definition_stack);
0 ignored issues
show
Unused Code introduced by
The assignment to $type is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $id is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
305
306 10
        return null;
307
    }
308
309
    /**
310
     * @inheritdoc
311
     */
312 10
    public function enterNode(\PhpParser\Node $node) {
313 10
        $start_line = $node->getAttribute("startLine");
314 10
        $end_line = $node->getAttribute("endLine");
315
316 10
        if ($this->is_definition($node)) {
317 9
            $type = $this->get_type_of($node);
318 9
            $id = $this->insert->definition
319 9
                ( $node->name
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
320 9
                , $type
321 9
                , $this->file_path
322 9
                , $start_line
323 9
                , $end_line
324 9
                );
325 9
            $this->call_definition_listener("listeners_enter_definition",  $type, $id, $node);
326 9
            $this->definition_stack[] = array($type, $id);
327 9
        }
328
        else {
329 7
            $this->call_misc_listener("listeners_enter_misc", $node);
330
        }
331 10
    }
332
333 9
    protected function get_type_of(\PhpParser\Node $node) {
334
        // Class
335 9
        if ($node instanceof N\Stmt\Class_) {
336 3
            return Variable::CLASS_TYPE;
337
        }
338
        // Method or Function
339 8
        elseif ($node instanceof N\Stmt\ClassMethod) {
340 2
            return Variable::METHOD_TYPE;
341
        }
342 6
        elseif ($node instanceof N\Stmt\Function_) {
343 6
            return Variable::FUNCTION_TYPE;
344
        }
345
        throw \InvalidArgumentException("'".get_class($node)."' has no type.");
346
    }
347
348 10
    protected function is_definition(\PhpParser\Node $node) {
349
        return     $node instanceof N\Stmt\Class_
350 10
                || $node instanceof N\Stmt\ClassMethod
351 10
                || $node instanceof N\Stmt\Function_;
352
    }
353
354
    /**
355
     * @inheritdoc
356
     */
357 10
    public function leaveNode(\PhpParser\Node $node) {
358
        // Class
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
359
/*        if($this->is_definition($node)) {
360
            list($type, $id) = array_pop($this->definition_stack);
361
            $this->call_definition_listener("listeners_leave_definition", $type, $id, $node);
362
        }
363
        else {
364
            $this->call_misc_listener("listeners_leave_misc", $node);
365
        }*/
366 10
    }
367
}
368