Completed
Push — master ( 608de6...159902 )
by Richard
05:43
created

Indexer::on_enter_misc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 3
nc 1
nop 2
crap 1
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 licence along with the code.
9
 */
10
11
namespace Lechimp\Dicto\Indexer;
12
13
use Lechimp\Dicto\Indexer as I;
14
use Lechimp\Dicto\Variables\Variable;
15
use PhpParser\Node as N;
16
17
/**
18
 * Implementation of Indexer with PhpParser.
19
 */
20
class Indexer implements Location, ListenerRegistry, \PhpParser\NodeVisitor {
21
    /**
22
     * @var string
23
     */
24
    protected $project_root_path;
25
26
    /**
27
     * @var I\Insert|null
28
     */
29
    protected $insert;
30
31
    /**
32
     * @var \PhpParser\Parser
33
     */
34
    protected $parser;
35
36
    /**
37
     * @var array   string => array()
38
     */
39
    protected $listeners_enter_entity;
40
41
    /**
42
     * @var array   string => array()
43
     */
44
    protected $listeners_leave_entity;
45
46
    /**
47
     * @var array   string => array()
48
     */
49
    protected $listeners_enter_misc;
50
51
    /**
52
     * @var array   string => array()
53
     */
54
    protected $listeners_leave_misc;
55
56
    // state for parsing a file
57
58
    /**
59
     * @var string|null
60
     */
61
    protected $file_path = null;
62
63
    /**
64
     * @var string|null
65
     */
66
    protected $file_content = null;
67
68
    /**
69
     * This contains the stack of ids were currently in, i.e. the nesting of
70
     * known code blocks we are in.
71
     *
72
     * @var array|null  contain ($entity_type, $entity_id)
73
     */
74
    protected $entity_stack = null;
75
76
    /**
77
     * @param   string  $project_root_path
78
     */
79 24
    public function __construct(\PhpParser\Parser $parser, $project_root_path, Insert $insert) {
80 24
        $this->parser = $parser;
81 24
        assert('is_string($project_root_path)');
82 24
        $this->project_root_path = $project_root_path;
83 24
        $this->insert = $insert;
84 24
        $this->listeners_enter_entity = array
85 24
            ( 0 => array()
86 24
            );
87 24
        $this->listeners_leave_entity = array
88 24
            ( 0 => array()
89 24
            );
90 24
        $this->listeners_enter_misc = array
91 24
            ( 0 => array()
92 24
            );
93 24
        $this->listeners_leave_misc = array
94 24
            ( 0 => array()
95 24
            );
96 24
    }
97
98
    /**
99
     * @param   string  $path
100
     */
101 23
    public function index_file($path) {
102 23
        $content = file_get_contents($this->project_root_path."/$path");
103 23
        if ($content === false) {
104
            throw \InvalidArgumentException("Can't read file $path.");
105
        }
106
107 23
        $stmts = $this->parser->parse($content);
108 23
        if ($stmts === null) {
109
            throw new \RuntimeException("Can't parse file $path.");
110
        }
111
112 23
        $traverser = new \PhpParser\NodeTraverser;
113 23
        $traverser->addVisitor($this);
114
115 23
        $this->entity_stack = array();
116 23
        $this->file_path = $path;
117 23
        $this->file_content = $content;
118 23
        $traverser->traverse($stmts);
119 23
    }
120
121
   // from ListenerRegistry 
122
123
    /**
124
     * @inheritdoc
125
     */
126 1
    public function on_enter_entity($types, \Closure $listener) {
127 1
        $this->on_enter_or_leave_something("listeners_enter_entity", $types, $listener);
128 1
        return $this;
129
    }
130
131
    /**
132
     * @inheritdoc
133
     */
134 1
    public function on_leave_entity($types, \Closure $listener) {
135 1
        $this->on_enter_or_leave_something("listeners_leave_entity", $types, $listener);
136 1
        return $this;
137
    }
138
139
    /**
140
     * @inheritdoc
141
     */
142 23
    public function on_enter_misc($classes, \Closure $listener) {
143 23
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
144 23
        return $this;
145
    }
146
147
    /**
148
     * @inheritdoc
149
     */
150 1
    public function on_leave_misc($classes, \Closure $listener) {
151 1
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
152 1
        return $this;
153
    }
154
155
    // generalizes over over on_enter/leave_xx
156
157
    /**
158
     * @param   string      $what
159
     * @param   array|null  $things
160
     */
161 23
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
162 23
        $loc = &$this->$what;
163 23
        if ($things === null) {
164 1
            $loc[0][] = $listener;
165 1
        }
166
        else {
167 23
            foreach ($things as $thing) {
168 23
                assert('is_string($thing)');
169 23
                if (!array_key_exists($thing, $loc)) {
170 23
                    $loc[$thing] = array();
171 23
                }
172 23
                $loc[$thing][] = $listener;
173 23
            }
174
        }
175 23
    }
176
177
    // generalizes over calls to misc listeners
178
179
    /**
180
     * @param   string          $which
181
     * @param   \PhpParser\Node $node
182
     */
183 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...
184 23
        $listeners = &$this->$which;
185 23
        foreach ($listeners[0] as $listener) {
186
            $listener($this->insert, $this, $node);
187 23
        }
188 23
        $cls = get_class($node);
189 23
        if (array_key_exists($cls, $listeners)) {
190 21
            foreach ($listeners[$cls] as $listener) {
191 21
                $listener($this->insert, $this, $node);
192 21
            }
193 21
        }
194 23
    }
195
196
    /**
197
     * @param   string                  $which
198
     * @param   string                  $type
199
     * @param   int                     $type
200
     * @param   \PhpParser\Node|null    $node
201
     */
202 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...
203 23
        $listeners = &$this->$which;
204 23
        foreach ($listeners[0] as $listener) {
205 1
            $listener($this->insert, $this, $type, $id, $node);
206 23
        }
207 23
        if (array_key_exists($type, $listeners)) {
208
            foreach ($listeners[$type] as $listener) {
209
                $listener($this->insert, $this, $type, $id, $node);
210
            }
211
        }
212 23
    }
213
214
   // from Location
215
216
    /**
217
     * @inheritdoc
218
     */
219 17
    public function file_path() {
220 17
        return $this->file_path;
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226 17
    public function in_entities() {
227 17
        return $this->entity_stack;
228
    }
229
230
    // from \PhpParser\NodeVisitor
231
232
    /**
233
     * @inheritdoc
234
     */
235 23
    public function beforeTraverse(array $nodes) {
236 23
        $this->insert->source_file($this->file_path, $this->file_content);
237
238
        // for sure found a file
239 23
        $id = $this->insert->entity
240 23
            ( Variable::FILE_TYPE
241 23
            , $this->file_path
242 23
            , $this->file_path
243 23
            , 1
244 23
            , substr_count($this->file_content, "\n") + 1
245 23
            );
246
247 23
        $this->entity_stack[] = array(Variable::FILE_TYPE, $id);
248
249 23
        $this->call_entity_listener("listeners_enter_entity", Variable::FILE_TYPE, $id, null);
250
251 23
        return null;
252
    }
253
254
    /**
255
     * @inheritdoc
256
     */
257 23
    public function afterTraverse(array $nodes) {
258 23
        list($type, $id) = array_pop($this->entity_stack);
259
260 23
        $this->call_entity_listener("listeners_leave_entity", $type, $id, null);
261
262 23
        return null;
263
    }
264
265
    /**
266
     * @inheritdoc
267
     */
268 23
    public function enterNode(\PhpParser\Node $node) {
269 23
        $start_line = $node->getAttribute("startLine");
270 23
        $end_line = $node->getAttribute("endLine");
271
272
        // Class
273 23
        if ($this->is_entity($node)) {
274 22
            $type = $this->get_type_of($node);
275 22
            $id = $this->insert->entity
276 22
                ( $type
277 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...
278 22
                , $this->file_path
279 22
                , $start_line
280 22
                , $end_line
281 22
                );
282 22
            $this->call_entity_listener("listeners_enter_entity",  $type, $id, $node);
283 22
            $this->entity_stack[] = array($type, $id);
284 22
        }
285
        else {
286 23
            $this->call_misc_listener("listeners_enter_misc", $node);
287
        }
288 23
    }
289
290 22
    protected function get_type_of(\PhpParser\Node $node) {
291
        // Class
292 22
        if ($node instanceof N\Stmt\Class_) {
293 22
            return Variable::CLASS_TYPE;
294
        }
295
        // Method or Function
296 22
        elseif ($node instanceof N\Stmt\ClassMethod) {
297 22
            return Variable::METHOD_TYPE;
298
        }
299
        elseif ($node instanceof N\Stmt\Function_) {
300
            return Variable::FUNCTION_TYPE;
301
        }
302
        throw \InvalidArgumentException("'".get_class($node)."' has no type.");
303
    }
304
305 23
    protected function is_entity(\PhpParser\Node $node) {
306
        return     $node instanceof N\Stmt\Class_
307 23
                || $node instanceof N\Stmt\ClassMethod
308 23
                || $node instanceof N\Stmt\Function_;
309
    }
310
311
    /**
312
     * @inheritdoc
313
     */
314 23
    public function leaveNode(\PhpParser\Node $node) {
315
        // Class
316 23
        if($this->is_entity($node)) {
317 22
            list($type, $id) = array_pop($this->entity_stack);
318 22
            $this->call_entity_listener("listeners_leave_entity", $type, $id, $node);
319 22
        }
320
        else {
321 23
            $this->call_misc_listener("listeners_leave_misc", $node);
322
        }
323 23
    }
324
}
325