Analyser   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 297
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 24
c 4
b 0
f 0
lcom 2
cbo 7
dl 0
loc 297
ccs 65
cts 65
cp 1
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A getNodeTraverser() 0 8 2
A hasNodeTraverserAttached() 0 4 1
A initDefaultTraverser() 0 4 1
A setTraverser() 0 4 1
A attachRequirementVisitor() 0 7 1
A attachRequirementVisitors() 0 8 2
A run() 0 13 2
A isAnalyserRun() 0 4 2
A getResult() 0 9 2
A setResultInstance() 0 13 3
parse() 0 1 ?
createAnalysisTargetId() 0 1 ?
A getParser() 0 8 2
A setParser() 0 4 1
A hasParserAttached() 0 4 1
A initDefaultParser() 0 6 1
1
<?php
2
/**
3
 * Analyser.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained through one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
namespace Pvra;
18
19
20
use PhpParser\NodeTraverser;
21
use PhpParser\NodeTraverserInterface;
22
use PhpParser\NodeVisitor;
23
use PhpParser\Parser;
24
use PhpParser\ParserFactory;
25
use Pvra\Lexer\ExtendedEmulativeLexer;
26
27
/**
28
 * Class Analyser
29
 *
30
 * @package Pvra
31
 */
32
abstract class Analyser
33
{
34
    /**
35
     * The instance of Parser used to run this analysis.
36
     *
37
     * @var Parser
38
     */
39
    private $parser;
40
41
    /**
42
     * The NodeTraverserInterface used to run this analysis.
43
     *
44
     * @var NodeTraverserInterface
45
     */
46
    private $nodeTraverser;
47
48
    /**
49
     * The result-instance used to store results from this analysis.
50
     *
51
     * @var AnalysisResult
52
     */
53
    private $result;
54
55
    /**
56
     * Analyser constructor
57
     *
58
     * This constructor registers the `NodeVisitor\NameResolver` node walker as first node traverser if the first
59
     * argument is set to true. Refer to the parameters documentation.
60
     *
61
     * @param bool $registerNameResolver If set to true `PhpParser\NodeVisitor\NameResolver` will be added as the first
62
     * visitor. This may negatively affect performance, some Visitors depend on resolved names, however.
63
     */
64 90
    public function __construct($registerNameResolver = true)
65
    {
66 90
        if ($registerNameResolver === true) {
67 68
            $this->getNodeTraverser()->addVisitor(new NodeVisitor\NameResolver());
68 34
        }
69 90
    }
70
71
    /**
72
     * Gets the associated NodeTraverser
73
     *
74
     * This method returns the associated NodeTraverser. If no NodeTraverser has been attached a default one will be
75
     * created.
76
     *
77
     * @see RequirementAnalyser::initDefaultTraverser Analyser::initDefaultTraverser
78
     *
79
     * @return NodeTraverserInterface The NodeTraverser associated to this instance.
80
     */
81 90
    public function getNodeTraverser()
82
    {
83 90
        if (!$this->hasNodeTraverserAttached()) {
84 90
            $this->initDefaultTraverser();
85 45
        }
86
87 90
        return $this->nodeTraverser;
88
    }
89
90
    /**
91
     * Check whether a NodeTraverser has been attached to the current Analyser
92
     *
93
     * Will return true if any traverser has previously been set. Will also return true if the
94
     * default/fallback traverser has been attached.
95
     *
96
     * @return bool Returns true when Analyser::$nodeTraverser has been initialized.
97
     */
98 90
    public function hasNodeTraverserAttached()
99
    {
100 90
        return $this->nodeTraverser !== null;
101
    }
102
103
    /**
104
     * Initiate this instance using a default Traverser
105
     *
106
     * This method will attach the NodeTraverser shipped within the PHP-Parser dependency as default traverser.
107
     * Usually `PhpParser\NodeTraverser`
108
     */
109 90
    private function initDefaultTraverser()
110
    {
111 90
        $this->setTraverser(new NodeTraverser);
112 90
    }
113
114
    /**
115
     * Set the NodeTraverser used by this instance
116
     *
117
     * This method allows you to set a `NodeTraverser` before the default one is initialized.
118
     * This should usually happen in the constructor of a child class.
119
     *
120
     * @param \PhpParser\NodeTraverser|\PhpParser\NodeTraverserInterface $nodeTraverser
121
     */
122 90
    protected function setTraverser(NodeTraverserInterface $nodeTraverser)
123
    {
124 90
        $this->nodeTraverser = $nodeTraverser;
125 90
    }
126
127
    /**
128
     * Attaches a RequirementVisitor.
129
     *
130
     * This method should be used to attach an instance of `Pvra\PhpParser\RequirementAnalyserAwareInterface` to the
131
     * current analyser. This method makes sure that RequirementAnalyserAwareInterface::setOwningAnalyser is
132
     * called using the correct parameters.
133
     *
134
     * @param NodeVisitor|AnalyserAwareInterface $visitor
135
     * @return $this Returns the current instance to allow chained calls.
136
     * @see RequirementAnalyserAwareInterface::setOwningAnalyser() Current instance is correctly attached.s
137
     */
138 54
    public function attachRequirementVisitor(AnalyserAwareInterface $visitor)
139
    {
140 54
        $visitor->setOwningAnalyser($this);
141 54
        $this->getNodeTraverser()->addVisitor($visitor);
142
143 54
        return $this;
144
    }
145
146
    /**
147
     * Attach an array of RequirementVisitors
148
     *
149
     * This method can be used to attach multiple `RequirementAnalyserAwareInterface` instances at once.
150
     * `Analyser::attachRequirementVisitor` is called upon each instance.
151
     *
152
     * @param AnalyserAwareInterface[] $visitors An array of requirement visitors
153
     * @return $this Returns the current instance to allow chained calls.
154
     * @see attachRequirementVisitor() Method containing implementation
155
     */
156 52
    public function attachRequirementVisitors(array $visitors)
157
    {
158 52
        foreach ($visitors as $visitor) {
159 52
            $this->attachRequirementVisitor($visitor);
160 26
        }
161
162 52
        return $this;
163
    }
164
165
    /**
166
     * Execute the current Analysis
167
     *
168
     * Parses the given code and runs the currently attached visitors. If run has already been called the previously
169
     * generated result will be returned. The result instance returned by this method is sealed.
170
     * Visitors that are attached **after** run is called are ignored on subsequent calls.
171
     *
172
     * @see RequirementAnalysisResult::seal
173
     *
174
     * @return AnalysisResult The sealed result.
175
     */
176 46
    public function run()
177
    {
178 46
        if (!$this->isAnalyserRun()) {
179 46
            $stmts = $this->parse();
180
181
            // RequirementAnalyserAwareInterface visitors will call getResult on this instance.
182 46
            $this->nodeTraverser->traverse($stmts);
183
184 46
            $this->getResult()->seal();
185 23
        }
186
187 46
        return $this->getResult();
188
    }
189
190
    /**
191
     * Determines of this analyser is already run
192
     *
193
     * The result is based on the presence of a `AnalysisResult` instance and its state.
194
     *
195
     * @return bool Whether the analyser has a result instance and it was sealed.
196
     */
197 48
    public function isAnalyserRun()
198
    {
199 48
        return $this->result instanceof AnalysisResult && $this->result->isSealed();
200
    }
201
202
    /**
203
     * Get the result instance associated with this Analyser
204
     *
205
     * If a `AnalysisResult` instance is attached it will be returned.
206
     * If none was attached a default one is attached. Please be aware that you cannot set
207
     * a custom `MessageFormatter` in an instance created using this.
208
     *
209
     * @return AnalysisResult The `AnalysisResult` instance attached with this
210
     * analyser
211
     *
212
     * @see setResultInstance() Used to set an instance of a result.
213
     */
214 62
    public function getResult()
215
    {
216 62
        if ($this->result === null) {
217 4
            $this->result = new AnalysisResult();
218 4
            $this->result->setAnalysisTargetId($this->createAnalysisTargetId());
219 2
        }
220
221 62
        return $this->result;
222
    }
223
224
    /**
225
     * Attach a (new) ResultInstance to this analyser
226
     *
227
     * Set a new result instance to this analyser. If a result was already attached or the to be attached result is
228
     * already sealed an exception is thrown.
229
     *
230
     * @param \Pvra\AnalysisResult $result The `AnalysisResult` to be
231
     * attached to this analyser
232
     * @return $this Returns the current instance to allow method chaining
233
     * @throws \Exception Thrown in case that the given result is sealed or an Result instance was already attached.
234
     *
235
     * @see RequirementAnalysisResult::setAnalysisTargetId() Method that is called on the attached result instance
236
     */
237 74
    public function setResultInstance(AnalysisResult $result)
238
    {
239 74
        if ($this->result !== null) {
240 2
            throw new \Exception('A result instance was already set. Overriding it may lead to data loss.');
241 72
        } elseif ($result->isSealed()) {
242 2
            throw new \LogicException('The attached Result instance is already sealed.');
243
        }
244
245 70
        $this->result = $result;
246 70
        $this->result->setAnalysisTargetId($this->createAnalysisTargetId());
247
248 70
        return $this;
249
    }
250
251
    /**
252
     * Parse the given code.
253
     *
254
     * Implementations of this method should parse the given code and return a list of `Node` instances.
255
     * The simplest implementation may directly return `return $this->getParser()->getParse($this->getCode());`.
256
     *
257
     * @return \PhpParser\Node[] List of nodes representing the analysing target.
258
     */
259
    protected abstract function parse();
260
261
    /**
262
     * Create an identifier for the parsed content.
263
     *
264
     * Implementations of this method should return a string that can be used to identify a given source.
265
     * This may be achieved by hashing a given string or returning an absolute path.
266
     *
267
     * @return string Identifier to identify the target of this analyser.
268
     */
269
    protected abstract function createAnalysisTargetId();
270
271
    /**
272
     * Get the currently injected parser or create a default instance
273
     *
274
     * This method will always return a valid `Parser` instance, even if none was injected.
275
     * In that case the return value of `initDefaultParser` will be returned
276
     *
277
     * @return Parser
278
     * @see setParser() Inject a custom `Parser` object
279
     * @see initDefaultParser() Create and set a fallback `Parser` object
280
     */
281 48
    public function getParser()
282
    {
283 48
        if (!$this->hasParserAttached()) {
284 48
            $this->initDefaultParser();
285 24
        }
286
287 48
        return $this->parser;
288
    }
289
290
    /**
291
     * Inject a custom `Parser` object
292
     *
293
     * Calling this method will override any previously injected parser object.
294
     *
295
     * @param Parser $parser
296
     */
297 48
    protected function setParser(Parser $parser)
298
    {
299 48
        $this->parser = $parser;
300 48
    }
301
302
    /**
303
     * Returns whether a parser was attached.
304
     *
305
     * Will return true if any parserhas previously been set. Will also return true if the
306
     * default/fallback parser has been attached.
307
     *
308
     * @return bool
309
     */
310 48
    public function hasParserAttached()
311
    {
312 48
        return $this->parser !== null;
313
    }
314
315
    /**
316
     * Set a fallback parser as parser for this Analyser instance
317
     *
318
     * The used `Parser` object will use the `ExtendedEmulativeLexer`.
319
     *
320
     * @see ExtendedEmulativeLexer Used lexer
321
     */
322 48
    private function initDefaultParser()
323
    {
324 48
        $this->setParser((new ParserFactory())
325 48
            ->create(ParserFactory::PREFER_PHP7, ExtendedEmulativeLexer::createDefaultInstance())
326 24
        );
327 48
    }
328
}
329