Passed
Push — main ( 28d658...6dddcc )
by Michiel
06:12
created

PHPMDTask::setCacheFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\Ext\Analyzer\Phpmd;
22
23
use Phing\Exception\BuildException;
24
use Phing\Io\File;
25
use Phing\Task;
26
use Phing\Type\Element\FileSetAware;
27
use Phing\Util\DataStore;
28
use PHPMD\AbstractRule;
29
use PHPMD\PHPMD;
30
use PHPMD\Report;
31
use PHPMD\RuleSetFactory;
32
33
/**
34
 * Runs PHP Mess Detector. Checking PHP files for several potential problems
35
 * based on rulesets.
36
 *
37
 * @package phing.tasks.ext.phpmd
38
 * @author  Benjamin Schultz <[email protected]>
39
 * @since   2.4.1
40
 */
41
class PHPMDTask extends Task
42
{
43
    use FileSetAware;
44
45
    /**
46
     * A php source code filename or directory
47
     *
48
     * @var File
49
     */
50
    protected $file = null;
51
52
    /**
53
     * The rule-set filenames or identifier.
54
     *
55
     * @var string
56
     */
57
    protected $rulesets = 'codesize,unusedcode';
58
59
    /**
60
     * The minimum priority for rules to load.
61
     *
62
     * @var integer
63
     */
64
    protected $minimumPriority = 0;
65
66
    /**
67
     * List of valid file extensions for analyzed files.
68
     *
69
     * @var array
70
     */
71
    protected $allowedFileExtensions = ['php'];
72
73
    /**
74
     * List of exclude directory patterns.
75
     *
76
     * @var array
77
     */
78
    protected $ignorePatterns = ['.git', '.svn', 'CVS', '.bzr', '.hg'];
79
80
    /**
81
     * The format for the report
82
     *
83
     * @var string
84
     */
85
    protected $format = 'text';
86
87
    /**
88
     * Formatter elements.
89
     *
90
     * @var PHPMDFormatterElement[]
91
     */
92
    protected $formatters = [];
93
94
    /**
95
     * @var string
96
     */
97
    protected $pharLocation = "";
98
99
    /**
100
     * Cache data storage
101
     *
102
     * @var DataStore
103
     */
104
    protected $cache;
105
106
    /**
107
     * Set the input source file or directory.
108
     *
109
     * @param File $file The input source file or directory.
110
     */
111
    public function setFile(File $file)
112
    {
113
        $this->file = $file;
114
    }
115
116
    /**
117
     * Sets the minimum rule priority.
118
     *
119
     * @param integer $minimumPriority Minimum rule priority.
120
     */
121
    public function setMinimumPriority($minimumPriority)
122
    {
123
        $this->minimumPriority = $minimumPriority;
124
    }
125
126
    /**
127
     * Sets the rule-sets.
128
     *
129
     * @param string $ruleSetFileNames Comma-separated string of rule-set filenames or identifier.
130
     */
131
    public function setRulesets(string $ruleSetFileNames)
132
    {
133
        $this->rulesets = $ruleSetFileNames;
134
    }
135
136
    /**
137
     * Sets a list of filename extensions for valid php source code files.
138
     *
139
     * @param string $fileExtensions List of valid file extensions without leading dot.
140
     */
141
    public function setAllowedFileExtensions($fileExtensions)
142
    {
143
        $this->allowedFileExtensions = [];
144
145
        $token = ' ,;';
146
        $ext = strtok($fileExtensions, $token);
147
148
        while ($ext !== false) {
149
            $this->allowedFileExtensions[] = $ext;
150
            $ext = strtok($token);
151
        }
152
    }
153
154
    /**
155
     * Sets a list of ignore patterns that is used to exclude directories from the source analysis.
156
     *
157
     * @param string $ignorePatterns List of ignore patterns.
158
     */
159
    public function setIgnorePatterns($ignorePatterns)
160
    {
161
        $this->ignorePatterns = [];
162
163
        $token = ' ,;';
164
        $pattern = strtok($ignorePatterns, $token);
165
166
        while ($pattern !== false) {
167
            $this->ignorePatterns[] = $pattern;
168
            $pattern = strtok($token);
169
        }
170
    }
171
172
    /**
173
     * Create object for nested formatter element.
174
     *
175
     * @return PHPMDFormatterElement
176
     */
177
    public function createFormatter()
178
    {
179
        $num = array_push($this->formatters, new PHPMDFormatterElement());
180
181
        return $this->formatters[$num - 1];
182
    }
183
184
    /**
185
     * @param string $format
186
     */
187
    public function setFormat($format)
188
    {
189
        $this->format = $format;
190
    }
191
192
    /**
193
     * @param string $pharLocation
194
     */
195
    public function setPharLocation($pharLocation)
196
    {
197
        $this->pharLocation = $pharLocation;
198
    }
199
200
    /**
201
     * Whether to store last-modified times in cache
202
     *
203
     * @param File $file
204
     */
205
    public function setCacheFile(File $file)
206
    {
207
        $this->cache = new DataStore($file);
208
    }
209
210
    /**
211
     * Find PHPMD
212
     *
213
     * @return string
214
     * @throws BuildException
215
     */
216
    protected function loadDependencies()
217
    {
218
        if (!empty($this->pharLocation)) {
219
            // nasty but necessary: reorder the autoloaders so the one in the PHAR gets priority
220
            $autoloadFunctions = spl_autoload_functions();
221
            $composerAutoloader = null;
222
            if (get_class($autoloadFunctions[0][0]) === 'Composer\Autoload\ClassLoader') {
223
                $composerAutoloader = $autoloadFunctions[0];
224
                spl_autoload_unregister($composerAutoloader);
225
            }
226
227
            $GLOBALS['_SERVER']['SCRIPT_NAME'] = '-';
228
            ob_start();
229
            include_once 'phar://' . $this->pharLocation . '/vendor/autoload.php';
230
            ob_end_clean();
231
232
            if ($composerAutoloader !== null) {
233
                spl_autoload_register($composerAutoloader);
234
            }
235
        }
236
237
        $className = '\PHPMD\PHPMD';
238
239
        if (!class_exists($className)) {
240
            throw new BuildException(
241
                'PHPMDTask depends on PHPMD being installed and on include_path or listed in pharLocation.',
242
                $this->getLocation()
243
            );
244
        }
245
246
        $minPriority = AbstractRule::LOWEST_PRIORITY;
247
248
        if (!$this->minimumPriority) {
249
            $this->minimumPriority = $minPriority;
250
        }
251
252
        return $className;
253
    }
254
255
    /**
256
     * Return the list of files to parse
257
     *
258
     * @return string[] list of absolute files to parse
259
     */
260
    protected function getFilesToParse()
261
    {
262
        $filesToParse = [];
263
264
        if ($this->file instanceof File) {
0 ignored issues
show
introduced by
$this->file is always a sub-type of Phing\Io\File.
Loading history...
265
            $filesToParse[] = $this->file->getPath();
266
        } else {
267
            // append any files in filesets
268
            foreach ($this->filesets as $fs) {
269
                $dir = $fs->getDir($this->project)->getAbsolutePath();
270
                foreach ($fs->getDirectoryScanner($this->project)->getIncludedFiles() as $filename) {
271
                    $fileAbsolutePath = $dir . DIRECTORY_SEPARATOR . $filename;
272
                    if ($this->cache) {
273
                        $lastMTime = $this->cache->get($fileAbsolutePath);
274
                        $currentMTime = filemtime($fileAbsolutePath);
275
                        if ($lastMTime >= $currentMTime) {
276
                            continue;
277
                        }
278
279
                        $this->cache->put($fileAbsolutePath, $currentMTime);
280
                    }
281
                    $filesToParse[] = $fileAbsolutePath;
282
                }
283
            }
284
        }
285
        return $filesToParse;
286
    }
287
288
    /**
289
     * @return \PHPMD\RuleSet[]
290
     */
291
    protected function getRuleSets(): array
292
    {
293
        $ruleSetFactory = new RuleSetFactory();
294
        $ruleSetFactory->setMinimumPriority($this->minimumPriority);
295
        $rulesets = $ruleSetFactory->createRuleSets($this->rulesets);
296
        return $rulesets;
297
    }
298
299
    /**
300
     * Executes PHPMD against PhingFile or a FileSet
301
     *
302
     * @throws BuildException - if the phpmd classes can't be loaded.
303
     */
304
    public function main()
305
    {
306
        $className = $this->loadDependencies();
307
308
        if (!isset($this->file) and count($this->filesets) == 0) {
309
            throw new BuildException('Missing either a nested fileset or attribute "file" set');
310
        }
311
312
        if (count($this->formatters) == 0) {
313
            // turn legacy format attribute into formatter
314
            $fmt = new PHPMDFormatterElement();
315
            $fmt->setType($this->format);
316
            $fmt->setUseFile(false);
317
318
            $this->formatters[] = $fmt;
319
        }
320
321
        $reportRenderers = [];
322
323
        foreach ($this->formatters as $fe) {
324
            if ($fe->getType() == '') {
325
                throw new BuildException('Formatter missing required "type" attribute.');
326
            }
327
328
            if ($fe->getUsefile() && $fe->getOutfile() === null) {
329
                throw new BuildException('Formatter requires "outfile" attribute when "useFile" is true.');
330
            }
331
332
            $reportRenderers[] = $fe->getRenderer();
333
        }
334
335
        if ($this->cache) {
336
            $reportRenderers[] = new PHPMDRendererRemoveFromCache($this->cache);
337
        } else {
338
            $this->cache = null; // cache not compatible to old version
339
        }
340
341
        $rulesets = $this->getRuleSets();
342
343
        /**
344
         * @var PHPMD $phpmd
345
         */
346
        $phpmd = new $className();
347
        $phpmd->setFileExtensions($this->allowedFileExtensions);
348
        $phpmd->addIgnorePatterns($this->ignorePatterns);
349
350
        $filesToParse = $this->getFilesToParse();
351
352
        if (count($filesToParse) > 0) {
353
            $inputPath = implode(',', $filesToParse);
354
355
            $this->log('Processing files...');
356
357
            $report = new Report();
358
            $phpmd->processFiles($inputPath, $this->ignorePatterns, $reportRenderers, $rulesets, $report);
359
360
            if ($this->cache) {
361
                $this->cache->commit();
362
            }
363
364
            $this->log('Finished processing files');
365
        } else {
366
            $this->log('No files to process');
367
        }
368
    }
369
}
370