Completed
Push — master ( 8418b3...91ec6e )
by
unknown
13s queued 11s
created

PDepend/Metrics/Analyzer/ClassLevelAnalyzer.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of PDepend.
4
 *
5
 * PHP Version 5
6
 *
7
 * Copyright (c) 2008-2017 Manuel Pichler <[email protected]>.
8
 * All rights reserved.
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions
12
 * are met:
13
 *
14
 *   * Redistributions of source code must retain the above copyright
15
 *     notice, this list of conditions and the following disclaimer.
16
 *
17
 *   * Redistributions in binary form must reproduce the above copyright
18
 *     notice, this list of conditions and the following disclaimer in
19
 *     the documentation and/or other materials provided with the
20
 *     distribution.
21
 *
22
 *   * Neither the name of Manuel Pichler nor the names of his
23
 *     contributors may be used to endorse or promote products derived
24
 *     from this software without specific prior written permission.
25
 *
26
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
 * POSSIBILITY OF SUCH DAMAGE.
38
 *
39
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
40
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
41
 */
42
43
namespace PDepend\Metrics\Analyzer;
44
45
use PDepend\Metrics\AbstractAnalyzer;
46
use PDepend\Metrics\AggregateAnalyzer;
47
use PDepend\Metrics\AnalyzerFilterAware;
48
use PDepend\Metrics\AnalyzerNodeAware;
49
use PDepend\Source\AST\AbstractASTType;
50
use PDepend\Source\AST\ASTArtifact;
51
use PDepend\Source\AST\ASTArtifactList;
52
use PDepend\Source\AST\ASTClass;
53
use PDepend\Source\AST\ASTInterface;
54
use PDepend\Source\AST\ASTMethod;
55
use PDepend\Source\AST\ASTProperty;
56
use PDepend\Source\AST\ASTTrait;
57
58
/**
59
 * Generates some class level based metrics. This analyzer is based on the
60
 * metrics specified in the following document.
61
 *
62
 * http://www.aivosto.com/project/help/pm-oo-misc.html
63
 *
64
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
65
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
66
 */
67
class ClassLevelAnalyzer extends AbstractAnalyzer implements AggregateAnalyzer, AnalyzerFilterAware, AnalyzerNodeAware
68
{
69
    /**
70
     * Metrics provided by the analyzer implementation.
71
     */
72
    const M_IMPLEMENTED_INTERFACES       = 'impl',
73
          M_CLASS_INTERFACE_SIZE         = 'cis',
74
          M_CLASS_SIZE                   = 'csz',
75
          M_NUMBER_OF_PUBLIC_METHODS     = 'npm',
76
          M_PROPERTIES                   = 'vars',
77
          M_PROPERTIES_INHERIT           = 'varsi',
78
          M_PROPERTIES_NON_PRIVATE       = 'varsnp',
79
          M_WEIGHTED_METHODS             = 'wmc',
80
          M_WEIGHTED_METHODS_INHERIT     = 'wmci',
81
          M_WEIGHTED_METHODS_NON_PRIVATE = 'wmcnp';
82
83
    /**
84
     * Hash with all calculated node metrics.
85
     *
86
     * <code>
87
     * array(
88
     *     '0375e305-885a-4e91-8b5c-e25bda005438'  =>  array(
89
     *         'loc'    =>  42,
90
     *         'ncloc'  =>  17,
91
     *         'cc'     =>  12
92
     *     ),
93
     *     'e60c22f0-1a63-4c40-893e-ed3b35b84d0b'  =>  array(
94
     *         'loc'    =>  42,
95
     *         'ncloc'  =>  17,
96
     *         'cc'     =>  12
97
     *     )
98
     * )
99
     * </code>
100
     *
101
     * @var array(string=>array)
102
     */
103
    private $nodeMetrics = null;
104
105
    /**
106
     * The internal used cyclomatic complexity analyzer.
107
     *
108
     * @var \PDepend\Metrics\Analyzer\CyclomaticComplexityAnalyzer
109
     */
110
    private $cyclomaticAnalyzer = null;
111
112
    /**
113
     * Processes all {@link \PDepend\Source\AST\ASTNamespace} code nodes.
114
     *
115
     * @param  \PDepend\Source\AST\ASTNamespace[] $namespaces
116
     * @return void
117
     */
118 76
    public function analyze($namespaces)
119
    {
120 76
        if ($this->nodeMetrics === null) {
121
            // First check for the require cc analyzer
122 76
            if ($this->cyclomaticAnalyzer === null) {
123 2
                throw new \RuntimeException('Missing required CC analyzer.');
124
            }
125
126 74
            $this->fireStartAnalyzer();
127
128 74
            $this->cyclomaticAnalyzer->analyze($namespaces);
129
130
            // Init node metrics
131 74
            $this->nodeMetrics = array();
132
133
            // Visit all nodes
134 74
            foreach ($namespaces as $namespace) {
135 74
                $namespace->accept($this);
136 74
            }
137
138 74
            $this->fireEndAnalyzer();
139 74
        }
140 74
    }
141
142
    /**
143
     * This method must return an <b>array</b> of class names for required
144
     * analyzers.
145
     *
146
     * @return array(string)
0 ignored issues
show
The doc-type array(string) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
147
     */
148 2
    public function getRequiredAnalyzers()
149
    {
150 2
        return array('PDepend\\Metrics\\Analyzer\\CyclomaticComplexityAnalyzer');
151
    }
152
153
    /**
154
     * Adds a required sub analyzer.
155
     *
156
     * @param  \PDepend\Metrics\Analyzer $analyzer The sub analyzer instance.
157
     * @return void
158
     */
159 76
    public function addAnalyzer(\PDepend\Metrics\Analyzer $analyzer)
160
    {
161 76
        if ($analyzer instanceof \PDepend\Metrics\Analyzer\CyclomaticComplexityAnalyzer) {
162 74
            $this->cyclomaticAnalyzer = $analyzer;
163 74
        } else {
164 2
            throw new \InvalidArgumentException('CC Analyzer required.');
165
        }
166 74
    }
167
168
    /**
169
     * This method will return an <b>array</b> with all generated metric values
170
     * for the given <b>$node</b>. If there are no metrics for the requested
171
     * node, this method will return an empty <b>array</b>.
172
     *
173
     * @param  \PDepend\Source\AST\ASTArtifact $artifact
174
     * @return array(string=>mixed)
0 ignored issues
show
The doc-type array(string=>mixed) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
175
     */
176 74 View Code Duplication
    public function getNodeMetrics(ASTArtifact $artifact)
177
    {
178 74
        $metrics = array();
179 74
        if (isset($this->nodeMetrics[$artifact->getId()])) {
180 74
            $metrics = $this->nodeMetrics[$artifact->getId()];
181 74
        }
182 74
        return $metrics;
183
    }
184
185
    /**
186
     * Visits a class node.
187
     *
188
     * @param  \PDepend\Source\AST\ASTClass $class
189
     * @return void
190
     */
191 72
    public function visitClass(ASTClass $class)
192
    {
193 72
        $this->fireStartClass($class);
194
195 72
        $impl  = $class->getInterfaces()->count();
196 72
        $varsi = $this->calculateVarsi($class);
197 72
        $wmci  = $this->calculateWmciForClass($class);
198
199 72
        $this->nodeMetrics[$class->getId()] = array(
200 72
            self::M_IMPLEMENTED_INTERFACES       => $impl,
201 72
            self::M_CLASS_INTERFACE_SIZE         => 0,
202 72
            self::M_CLASS_SIZE                   => 0,
203 72
            self::M_NUMBER_OF_PUBLIC_METHODS     => 0,
204 72
            self::M_PROPERTIES                   => 0,
205 72
            self::M_PROPERTIES_INHERIT           => $varsi,
206 72
            self::M_PROPERTIES_NON_PRIVATE       => 0,
207 72
            self::M_WEIGHTED_METHODS             => 0,
208 72
            self::M_WEIGHTED_METHODS_INHERIT     => $wmci,
209 72
            self::M_WEIGHTED_METHODS_NON_PRIVATE => 0
210 72
        );
211
212 72
        foreach ($class->getProperties() as $property) {
213 40
            $property->accept($this);
214 72
        }
215 72
        foreach ($class->getMethods() as $method) {
216 50
            $method->accept($this);
217 72
        }
218
219 72
        $this->fireEndClass($class);
220 72
    }
221
222
    /**
223
     * Visits a code interface object.
224
     *
225
     * @param  \PDepend\Source\AST\ASTInterface $interface
226
     * @return void
227
     */
228 6
    public function visitInterface(ASTInterface $interface)
229
    {
230
        // Empty visit method, we don't want interface metrics
231 6
    }
232
233
    /**
234
     * Visits a trait node.
235
     *
236
     * @param  \PDepend\Source\AST\ASTTrait $trait
237
     * @return void
238
     * @since  1.0.0
239
     */
240 2
    public function visitTrait(ASTTrait $trait)
241
    {
242 2
        $this->fireStartTrait($trait);
243
244 2
        $wmci = $this->calculateWmciForTrait($trait);
245
246 2
        $this->nodeMetrics[$trait->getId()] = array(
247 2
            self::M_IMPLEMENTED_INTERFACES       => 0,
248 2
            self::M_CLASS_INTERFACE_SIZE         => 0,
249 2
            self::M_CLASS_SIZE                   => 0,
250 2
            self::M_NUMBER_OF_PUBLIC_METHODS     => 0,
251 2
            self::M_PROPERTIES                   => 0,
252 2
            self::M_PROPERTIES_INHERIT           => 0,
253 2
            self::M_PROPERTIES_NON_PRIVATE       => 0,
254 2
            self::M_WEIGHTED_METHODS             => 0,
255 2
            self::M_WEIGHTED_METHODS_INHERIT     => $wmci,
256 2
            self::M_WEIGHTED_METHODS_NON_PRIVATE => 0
257 2
        );
258
259 2
        foreach ($trait->getProperties() as $property) {
260
            $property->accept($this);
261 2
        }
262 2
        foreach ($trait->getMethods() as $method) {
263 2
            $method->accept($this);
264 2
        }
265
266 2
        $this->fireEndTrait($trait);
267 2
    }
268
269
    /**
270
     * Visits a method node.
271
     *
272
     * @param  \PDepend\Source\AST\ASTMethod $method
273
     * @return void
274
     */
275 52
    public function visitMethod(ASTMethod $method)
276
    {
277 52
        $this->fireStartMethod($method);
278
279 52
        $id = $method->getParent()->getId();
280
281 52
        $ccn = $this->cyclomaticAnalyzer->getCcn2($method);
282
283
        // Increment Weighted Methods Per Class(WMC) value
284 52
        $this->nodeMetrics[$id][self::M_WEIGHTED_METHODS] += $ccn;
285
        // Increment Class Size(CSZ) value
286 52
        ++$this->nodeMetrics[$id][self::M_CLASS_SIZE];
287
288
        // Increment Non Private values
289 52
        if ($method->isPublic()) {
290 48
            ++$this->nodeMetrics[$id][self::M_NUMBER_OF_PUBLIC_METHODS];
291
            // Increment Non Private WMC value
292 48
            $this->nodeMetrics[$id][self::M_WEIGHTED_METHODS_NON_PRIVATE] += $ccn;
293
            // Increment Class Interface Size(CIS) value
294 48
            ++$this->nodeMetrics[$id][self::M_CLASS_INTERFACE_SIZE];
295 48
        }
296
297 52
        $this->fireEndMethod($method);
298 52
    }
299
300
    /**
301
     * Visits a property node.
302
     *
303
     * @param  \PDepend\Source\AST\ASTProperty $property
304
     * @return void
305
     */
306 40
    public function visitProperty(ASTProperty $property)
307
    {
308 40
        $this->fireStartProperty($property);
309
310 40
        $id = $property->getDeclaringClass()->getId();
311
312
        // Increment VARS value
313 40
        ++$this->nodeMetrics[$id][self::M_PROPERTIES];
314
        // Increment Class Size(CSZ) value
315 40
        ++$this->nodeMetrics[$id][self::M_CLASS_SIZE];
316
317
        // Increment Non Private values
318 40
        if ($property->isPublic()) {
319
            // Increment Non Private VARS value
320 40
            ++$this->nodeMetrics[$id][self::M_PROPERTIES_NON_PRIVATE];
321
            // Increment Class Interface Size(CIS) value
322 40
            ++$this->nodeMetrics[$id][self::M_CLASS_INTERFACE_SIZE];
323 40
        }
324
325 40
        $this->fireEndProperty($property);
326 40
    }
327
328
    /**
329
     * Calculates the Variables Inheritance of a class metric, this method only
330
     * counts protected and public properties of parent classes.
331
     *
332
     * @param  \PDepend\Source\AST\ASTClass $class The context class instance.
333
     * @return integer
334
     */
335 72
    private function calculateVarsi(ASTClass $class)
336
    {
337
        // List of properties, this method only counts not overwritten properties
338 72
        $properties = array();
339
        // Collect all properties of the context class
340 72
        foreach ($class->getProperties() as $prop) {
341 40
            $properties[$prop->getName()] = true;
342 72
        }
343
344 72
        foreach ($class->getParentClasses() as $parent) {
345 32
            foreach ($parent->getProperties() as $prop) {
346 24
                if (!$prop->isPrivate() && !isset($properties[$prop->getName()])) {
347 20
                    $properties[$prop->getName()] = true;
348 20
                }
349 32
            }
350 72
        }
351 72
        return count($properties);
352
    }
353
354
    /**
355
     * Calculates the Weight Method Per Class metric, this method only counts
356
     * protected and public methods of parent classes.
357
     *
358
     * @param  \PDepend\Source\AST\ASTClass $class The context class instance.
359
     * @return integer
360
     */
361 72
    private function calculateWmciForClass(ASTClass $class)
362
    {
363 72
        $ccn = $this->calculateWmci($class);
364
365 72
        foreach ($class->getParentClasses() as $parent) {
366 32
            foreach ($parent->getMethods() as $method) {
367 18
                if ($method->isPrivate()) {
368 18
                    continue;
369
                }
370 18
                if (isset($ccn[($name = $method->getName())])) {
371 10
                    continue;
372
                }
373 12
                $ccn[$name] = $this->cyclomaticAnalyzer->getCcn2($method);
374 32
            }
375 72
        }
376
377 72
        return array_sum($ccn);
378
    }
379
380
    /**
381
     * Calculates the Weight Method Per Class metric for a trait.
382
     *
383
     * @param  \PDepend\Source\AST\ASTTrait $trait
384
     * @return integer
385
     * @since  1.0.6
386
     */
387 2
    private function calculateWmciForTrait(ASTTrait $trait)
388
    {
389 2
        return array_sum($this->calculateWmci($trait));
390
    }
391
392
    /**
393
     * Calculates the Weight Method Per Class metric.
394
     *
395
     * @param  \PDepend\Source\AST\AbstractASTType $type
396
     * @return integer[]
397
     * @since  1.0.6
398
     */
399 74
    private function calculateWmci(AbstractASTType $type)
400
    {
401 74
        $ccn = array();
402
403 74
        foreach ($type->getMethods() as $method) {
404 52
            $ccn[$method->getName()] = $this->cyclomaticAnalyzer->getCcn2($method);
405 74
        }
406
407 74
        return $ccn;
408
    }
409
}
410