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

php/PDepend/Metrics/Analyzer/CrapIndexAnalyzer.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\Analyzer;
48
use PDepend\Metrics\AnalyzerNodeAware;
49
use PDepend\Source\AST\AbstractASTCallable;
50
use PDepend\Source\AST\ASTArtifact;
51
use PDepend\Source\AST\ASTFunction;
52
use PDepend\Source\AST\ASTMethod;
53
54
/**
55
 * This analyzer calculates the C.R.A.P. index for methods an functions when a
56
 * clover coverage report was supplied. This report can be supplied by using the
57
 * command line option <b>--coverage-report=</b>.
58
 *
59
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
60
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
61
 */
62
class CrapIndexAnalyzer extends AbstractAnalyzer implements AggregateAnalyzer, AnalyzerNodeAware
63
{
64
    /**
65
     * Metrics provided by the analyzer implementation.
66
     */
67
    const M_CRAP_INDEX = 'crap',
68
          M_COVERAGE = 'cov';
69
70
    /**
71
     * The report option name.
72
     */
73
    const REPORT_OPTION = 'coverage-report';
74
75
    /**
76
     * Calculated crap metrics.
77
     *
78
     * @var array<string, array>
79
     */
80
    private $metrics = null;
81
82
    /**
83
     * The coverage report instance representing the supplied coverage report
84
     * file.
85
     *
86
     * @var \PDepend\Util\Coverage\Report
87
     */
88
    private $report = null;
89
90
    /**
91
     *
92
     * @var \PDepend\Metrics\Analyzer
93
     */
94
    private $ccnAnalyzer = null;
95
96
    /**
97
     * Returns <b>true</b> when this analyzer is enabled.
98
     *
99
     * @return boolean
100
     */
101 18
    public function isEnabled()
102
    {
103 18
        return isset($this->options[self::REPORT_OPTION]);
104
    }
105
106
    /**
107
     * Returns the calculated metrics for the given node or an empty <b>array</b>
108
     * when no metrics exist for the given node.
109
     *
110
     * @param  \PDepend\Source\AST\ASTArtifact $artifact
111
     * @return array<string, float>
0 ignored issues
show
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
112
     */
113 14 View Code Duplication
    public function getNodeMetrics(ASTArtifact $artifact)
114
    {
115 14
        if (isset($this->metrics[$artifact->getId()])) {
116 10
            return $this->metrics[$artifact->getId()];
117
        }
118 4
        return array();
119
    }
120
121
    /**
122
     * Returns an array with analyzer class names that are required by the crap
123
     * index analyzers.
124
     *
125
     * @return array<string>
126
     */
127 2
    public function getRequiredAnalyzers()
128
    {
129 2
        return array('PDepend\\Metrics\\Analyzer\\CyclomaticComplexityAnalyzer');
130
    }
131
132
    /**
133
     * Adds an analyzer that this analyzer depends on.
134
     *
135
     * @param  \PDepend\Metrics\Analyzer $analyzer
136
     * @return void
137
     */
138 14
    public function addAnalyzer(Analyzer $analyzer)
139
    {
140 14
        $this->ccnAnalyzer = $analyzer;
141 14
    }
142
143
    /**
144
     * Performs the crap index analysis.
145
     *
146
     * @param  \PDepend\Source\AST\ASTNamespace[] $namespaces
147
     * @return void
148
     */
149 14
    public function analyze($namespaces)
150
    {
151 14
        if ($this->isEnabled() && $this->metrics === null) {
152 14
            $this->doAnalyze($namespaces);
153 14
        }
154 14
    }
155
156
    /**
157
     * Performs the crap index analysis.
158
     *
159
     * @param  \PDepend\Source\AST\ASTNamespace[] $namespaces
160
     * @return void
161
     */
162 14
    private function doAnalyze($namespaces)
163
    {
164 14
        $this->metrics = array();
165
        
166 14
        $this->ccnAnalyzer->analyze($namespaces);
167
168 14
        $this->fireStartAnalyzer();
169
170 14
        foreach ($namespaces as $namespace) {
171 14
            $namespace->accept($this);
172 14
        }
173
174 14
        $this->fireEndAnalyzer();
175 14
    }
176
177
    /**
178
     * Visits the given method.
179
     *
180
     * @param  \PDepend\Source\AST\ASTMethod $method
181
     * @return void
182
     */
183 12
    public function visitMethod(ASTMethod $method)
184
    {
185 12
        if ($method->isAbstract() === false) {
186 8
            $this->visitCallable($method);
187 8
        }
188 12
    }
189
190
    /**
191
     * Visits the given function.
192
     *
193
     * @param  \PDepend\Source\AST\ASTFunction $function
194
     * @return void
195
     */
196 2
    public function visitFunction(ASTFunction $function)
197
    {
198 2
        $this->visitCallable($function);
199 2
    }
200
201
    /**
202
     * Visits the given callable instance.
203
     *
204
     * @param  \PDepend\Source\AST\AbstractASTCallable $callable
205
     * @return void
206
     */
207 10
    private function visitCallable(AbstractASTCallable $callable)
208
    {
209 10
        $this->metrics[$callable->getId()] = array(
210 10
            self::M_CRAP_INDEX => $this->calculateCrapIndex($callable),
211 10
            self::M_COVERAGE   => $this->calculateCoverage($callable)
212 10
        );
213 10
    }
214
215
    /**
216
     * Calculates the crap index for the given callable.
217
     *
218
     * @param  \PDepend\Source\AST\AbstractASTCallable $callable
219
     * @return float
220
     */
221 10
    private function calculateCrapIndex(AbstractASTCallable $callable)
222
    {
223 10
        $report = $this->createOrReturnCoverageReport();
224
225 10
        $complexity = $this->ccnAnalyzer->getCcn2($callable);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface PDepend\Metrics\Analyzer as the method getCcn2() does only exist in the following implementations of said interface: PDepend\Metrics\Analyzer...maticComplexityAnalyzer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
226 10
        $coverage   = $report->getCoverage($callable);
227
228 10
        if ($coverage == 0) {
229 6
            return pow($complexity, 2) + $complexity;
230 4
        } elseif ($coverage > 99.5) {
231 2
            return $complexity;
232
        }
233 2
        return pow($complexity, 2) * pow(1 - $coverage / 100, 3) + $complexity;
234
    }
235
236
    /**
237
     * Calculates the code coverage for the given callable object.
238
     *
239
     * @param  \PDepend\Source\AST\AbstractASTCallable $callable
240
     * @return float
241
     */
242 10
    private function calculateCoverage(AbstractASTCallable $callable)
243
    {
244 10
        return $this->createOrReturnCoverageReport()->getCoverage($callable);
245
    }
246
247
    /**
248
     * Returns a previously created report instance or creates a new report
249
     * instance.
250
     *
251
     * @return \PDepend\Util\Coverage\Report
252
     */
253 10
    private function createOrReturnCoverageReport()
254
    {
255 10
        if ($this->report === null) {
256 10
            $this->report = $this->createCoverageReport();
257 10
        }
258 10
        return $this->report;
259
    }
260
261
    /**
262
     * Creates a new coverage report instance.
263
     *
264
     * @return \PDepend\Util\Coverage\Report
265
     */
266 10
    private function createCoverageReport()
267
    {
268 10
        $factory = new \PDepend\Util\Coverage\Factory();
269 10
        return $factory->create($this->options['coverage-report']);
270
    }
271
}
272