Completed
Push — master ( dca6dc...178b4a )
by Richard
06:29
created

Indexer   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 347
Duplicated Lines 6.63 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 93.63%

Importance

Changes 40
Bugs 0 Features 1
Metric Value
wmc 40
c 40
b 0
f 1
lcom 1
cbo 11
dl 23
loc 347
ccs 147
cts 157
cp 0.9363
rs 8.2608

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
B index_directory() 0 22 4
A init_flightcontrol() 0 5 1
A index_file() 0 22 3
A on_enter_entity() 0 4 1
A on_leave_entity() 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_entity_listener() 11 11 4
A file_path() 0 3 1
A in_entities() 0 3 1
A beforeTraverse() 0 18 1
A afterTraverse() 0 7 1
A enterNode() 0 21 2
A get_type_of() 0 14 4
A is_entity() 0 5 3
A leaveNode() 0 10 2

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_entity;
45
46
    /**
47
     * @var array   string => array()
48
     */
49
    protected $listeners_leave_entity;
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 ($entity_type, $entity_id)
78
     */
79
    protected $entity_stack = null;
80
81 25
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
82 25
        $this->log = $log;
83 25
        $this->parser = $parser;
84 25
        $this->insert = $insert;
85 25
        $this->listeners_enter_entity = array
86 25
            ( 0 => array()
87 25
            );
88 25
        $this->listeners_leave_entity = array
89 25
            ( 0 => array()
90 25
            );
91 25
        $this->listeners_enter_misc = array
92 25
            ( 0 => array()
93 25
            );
94 25
        $this->listeners_leave_misc = array
95 25
            ( 0 => array()
96 25
            );
97 25
    }
98
99
    /**
100
     * Index a directory.
101
     *
102
     * @param   string  $path
103
     * @param   array   $ignore_paths
104
     * @return  null
105
     */
106 5
    public function index_directory($path, array $ignore_paths) {
107 5
        $fc = $this->init_flightcontrol($path);
108 5
        $fc->directory("/")
109 5
            ->recurseOn()
110
            ->filter(function(FSObject $obj) use (&$ignore_paths) {
111 5
                foreach ($ignore_paths as $pattern) {
112 5
                    if (preg_match("%$pattern%", $obj->path()) !== 0) {
113 5
                        return false;
114
                    }
115 5
                }
116 5
                return true;
117 5
            })
118 5
            ->foldFiles(null, function($_, File $file) use ($path) {
119
                try {
120 5
                    $this->index_file($path, $file->path());
121
                }
122 5
                catch (\PhpParser\Error $e) {
123 1
                    $this->log->error("in ".$file->path().": ".$e->getMessage());
124
                }
125 5
            });
126
127 5
    }
128
129
    /**
130
     * Initialize the filesystem abstraction.
131
     *
132
     * @return  Flightcontrol
133
     */
134 5
    public function init_flightcontrol($path) {
135 5
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
136 5
        $flysystem = new Filesystem($adapter);
137 5
        return new Flightcontrol($flysystem);
138
    }
139
140
    /**
141
     * @param   string  $base_dir
142
     * @param   string  $path
143
     * @return  null
144
     */
145 23
    public function index_file($base_dir, $path) {
146 23
        assert('is_string($base_dir)');
147 23
        assert('is_string($path)');
148 23
        $this->log->info("indexing: ".$path);
149 23
        $content = file_get_contents($base_dir."/$path");
150 23
        if ($content === false) {
151
            throw \InvalidArgumentException("Can't read file $base_dir/$path.");
152
        }
153
154 23
        $stmts = $this->parser->parse($content);
155 23
        if ($stmts === null) {
156
            throw new \RuntimeException("Can't parse file $base_dir/$path.");
157
        }
158
159 23
        $traverser = new \PhpParser\NodeTraverser;
160 23
        $traverser->addVisitor($this);
161
162 23
        $this->entity_stack = array();
163 23
        $this->file_path = $path;
164 23
        $this->file_content = $content;
165 23
        $traverser->traverse($stmts);
166 23
    }
167
168
   // from ListenerRegistry 
169
170
    /**
171
     * @inheritdoc
172
     */
173 1
    public function on_enter_entity($types, \Closure $listener) {
174 1
        $this->on_enter_or_leave_something("listeners_enter_entity", $types, $listener);
175 1
        return $this;
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181 1
    public function on_leave_entity($types, \Closure $listener) {
182 1
        $this->on_enter_or_leave_something("listeners_leave_entity", $types, $listener);
183 1
        return $this;
184
    }
185
186
    /**
187
     * @inheritdoc
188
     */
189 25
    public function on_enter_misc($classes, \Closure $listener) {
190 25
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
191 25
        return $this;
192
    }
193
194
    /**
195
     * @inheritdoc
196
     */
197 1
    public function on_leave_misc($classes, \Closure $listener) {
198 1
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
199 1
        return $this;
200
    }
201
202
    // generalizes over over on_enter/leave_xx
203
204
    /**
205
     * @param   string      $what
206
     * @param   array|null  $things
207
     */
208 25
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
209 25
        $loc = &$this->$what;
210 25
        if ($things === null) {
211 1
            $loc[0][] = $listener;
212 1
        }
213
        else {
214 25
            foreach ($things as $thing) {
215 25
                assert('is_string($thing)');
216 25
                if (!array_key_exists($thing, $loc)) {
217 25
                    $loc[$thing] = array();
218 25
                }
219 25
                $loc[$thing][] = $listener;
220 25
            }
221
        }
222 25
    }
223
224
    // generalizes over calls to misc listeners
225
226
    /**
227
     * @param   string          $which
228
     * @param   \PhpParser\Node $node
229
     */
230 23 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...
231 23
        $listeners = &$this->$which;
232 23
        foreach ($listeners[0] as $listener) {
233
            $listener($this->insert, $this, $node);
234 23
        }
235 23
        $cls = get_class($node);
236 23
        if (array_key_exists($cls, $listeners)) {
237 21
            foreach ($listeners[$cls] as $listener) {
238 21
                $listener($this->insert, $this, $node);
239 21
            }
240 21
        }
241 23
    }
242
243
    /**
244
     * @param   string                  $which
245
     * @param   string                  $type
246
     * @param   int                     $type
247
     * @param   \PhpParser\Node|null    $node
248
     */
249 23 View Code Duplication
    protected function call_entity_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...
250 23
        $listeners = &$this->$which;
251 23
        foreach ($listeners[0] as $listener) {
252 1
            $listener($this->insert, $this, $type, $id, $node);
253 23
        }
254 23
        if (array_key_exists($type, $listeners)) {
255
            foreach ($listeners[$type] as $listener) {
256
                $listener($this->insert, $this, $type, $id, $node);
257
            }
258
        }
259 23
    }
260
261
   // from Location
262
263
    /**
264
     * @inheritdoc
265
     */
266 17
    public function file_path() {
267 17
        return $this->file_path;
268
    }
269
270
    /**
271
     * @inheritdoc
272
     */
273 17
    public function in_entities() {
274 17
        return $this->entity_stack;
275
    }
276
277
    // from \PhpParser\NodeVisitor
278
279
    /**
280
     * @inheritdoc
281
     */
282 23
    public function beforeTraverse(array $nodes) {
283 23
        $this->insert->source_file($this->file_path, $this->file_content);
284
285
        // for sure found a file
286 23
        $id = $this->insert->entity
287 23
            ( Variable::FILE_TYPE
288 23
            , $this->file_path
289 23
            , $this->file_path
290 23
            , 1
291 23
            , substr_count($this->file_content, "\n") + 1
292 23
            );
293
294 23
        $this->entity_stack[] = array(Variable::FILE_TYPE, $id);
295
296 23
        $this->call_entity_listener("listeners_enter_entity", Variable::FILE_TYPE, $id, null);
297
298 23
        return null;
299
    }
300
301
    /**
302
     * @inheritdoc
303
     */
304 23
    public function afterTraverse(array $nodes) {
305 23
        list($type, $id) = array_pop($this->entity_stack);
306
307 23
        $this->call_entity_listener("listeners_leave_entity", $type, $id, null);
308
309 23
        return null;
310
    }
311
312
    /**
313
     * @inheritdoc
314
     */
315 23
    public function enterNode(\PhpParser\Node $node) {
316 23
        $start_line = $node->getAttribute("startLine");
317 23
        $end_line = $node->getAttribute("endLine");
318
319
        // Class
320 23
        if ($this->is_entity($node)) {
321 22
            $type = $this->get_type_of($node);
322 22
            $id = $this->insert->entity
323 22
                ( $type
324 22
                , $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...
325 22
                , $this->file_path
326 22
                , $start_line
327 22
                , $end_line
328 22
                );
329 22
            $this->call_entity_listener("listeners_enter_entity",  $type, $id, $node);
330 22
            $this->entity_stack[] = array($type, $id);
331 22
        }
332
        else {
333 23
            $this->call_misc_listener("listeners_enter_misc", $node);
334
        }
335 23
    }
336
337 22
    protected function get_type_of(\PhpParser\Node $node) {
338
        // Class
339 22
        if ($node instanceof N\Stmt\Class_) {
340 22
            return Variable::CLASS_TYPE;
341
        }
342
        // Method or Function
343 22
        elseif ($node instanceof N\Stmt\ClassMethod) {
344 22
            return Variable::METHOD_TYPE;
345
        }
346
        elseif ($node instanceof N\Stmt\Function_) {
347
            return Variable::FUNCTION_TYPE;
348
        }
349
        throw \InvalidArgumentException("'".get_class($node)."' has no type.");
350
    }
351
352 23
    protected function is_entity(\PhpParser\Node $node) {
353
        return     $node instanceof N\Stmt\Class_
354 23
                || $node instanceof N\Stmt\ClassMethod
355 23
                || $node instanceof N\Stmt\Function_;
356
    }
357
358
    /**
359
     * @inheritdoc
360
     */
361 23
    public function leaveNode(\PhpParser\Node $node) {
362
        // Class
363 23
        if($this->is_entity($node)) {
364 22
            list($type, $id) = array_pop($this->entity_stack);
365 22
            $this->call_entity_listener("listeners_leave_entity", $type, $id, $node);
366 22
        }
367
        else {
368 23
            $this->call_misc_listener("listeners_leave_misc", $node);
369
        }
370 23
    }
371
}
372