• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 17663621563

12 Sep 2025 03:25AM UTC coverage: 78.786%. Remained the same
17663621563

push

github

web-flow
Merge pull request #1243 from PHPCSStandards/phpcs-4.x/feature/155-normalize-some-code-style-rules-5

CS: normalize code style rules [5]

294 of 308 new or added lines in 191 files covered. (95.45%)

2354 existing lines in 130 files now uncovered.

19732 of 25045 relevant lines covered (78.79%)

96.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.1
/src/Reporter.php
1
<?php
2
/**
3
 * Manages reporting of errors and warnings.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer;
11

12
use PHP_CodeSniffer\Exceptions\DeepExitException;
13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Reports\Report;
16
use PHP_CodeSniffer\Util\Common;
17
use PHP_CodeSniffer\Util\ExitCode;
18

19
/**
20
 * Manages reporting of errors and warnings.
21
 *
22
 * @property-read int $totalFixable Total number of errors/warnings that can be fixed.
23
 * @property-read int $totalFixed   Total number of errors/warnings that were fixed.
24
 */
25
class Reporter
26
{
27

28
    /**
29
     * The config data for the run.
30
     *
31
     * @var \PHP_CodeSniffer\Config
32
     */
33
    public $config = null;
34

35
    /**
36
     * Total number of files that contain errors or warnings.
37
     *
38
     * @var integer
39
     */
40
    public $totalFiles = 0;
41

42
    /**
43
     * Total number of errors found during the run.
44
     *
45
     * @var integer
46
     */
47
    public $totalErrors = 0;
48

49
    /**
50
     * Total number of warnings found during the run.
51
     *
52
     * @var integer
53
     */
54
    public $totalWarnings = 0;
55

56
    /**
57
     * Total number of errors that can be fixed.
58
     *
59
     * @var integer
60
     */
61
    public $totalFixableErrors = 0;
62

63
    /**
64
     * Total number of warnings that can be fixed.
65
     *
66
     * @var integer
67
     */
68
    public $totalFixableWarnings = 0;
69

70
    /**
71
     * Total number of errors that were fixed.
72
     *
73
     * @var integer
74
     */
75
    public $totalFixedErrors = 0;
76

77
    /**
78
     * Total number of warnings that were fixed.
79
     *
80
     * @var integer
81
     */
82
    public $totalFixedWarnings = 0;
83

84
    /**
85
     * A cache of report objects.
86
     *
87
     * @var array
88
     */
89
    private $reports = [];
90

91
    /**
92
     * A cache of opened temporary files.
93
     *
94
     * @var array
95
     */
96
    private $tmpFiles = [];
97

98

99
    /**
100
     * Initialise the reporter.
101
     *
102
     * All reports specified in the config will be created and their
103
     * output file (or a temp file if none is specified) initialised by
104
     * clearing the current contents.
105
     *
106
     * @param \PHP_CodeSniffer\Config $config The config data for the run.
107
     *
108
     * @return void
109
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a custom report class could not be found.
110
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException  If a report class is incorrectly set up.
111
     */
112
    public function __construct(Config $config)
×
113
    {
114
        $this->config = $config;
×
115

116
        foreach ($config->reports as $type => $output) {
×
117
            if ($output === null) {
×
118
                $output = $config->reportFile;
×
119
            }
120

121
            $reportClassName = '';
×
122
            if (strpos($type, '.') !== false) {
×
123
                // This is a path to a custom report class.
124
                $filename = realpath($type);
×
125
                if ($filename === false) {
×
126
                    $error = "ERROR: Custom report \"$type\" not found" . PHP_EOL;
×
127
                    throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
128
                }
129

130
                $reportClassName = Autoload::loadFile($filename);
×
131
            } elseif (class_exists('PHP_CodeSniffer\Reports\\' . ucfirst($type)) === true) {
×
132
                // PHPCS native report.
133
                $reportClassName = 'PHP_CodeSniffer\Reports\\' . ucfirst($type);
×
134
            } elseif (class_exists($type) === true) {
×
135
                // FQN of a custom report.
136
                $reportClassName = $type;
×
137
            } else {
138
                // OK, so not a FQN, try and find the report using the registered namespaces.
139
                $registeredNamespaces = Autoload::getSearchPaths();
×
140
                $trimmedType          = ltrim($type, '\\');
×
141

142
                foreach ($registeredNamespaces as $nsPrefix) {
×
143
                    if ($nsPrefix === '') {
×
144
                        continue;
×
145
                    }
146

147
                    if (class_exists($nsPrefix . '\\' . $trimmedType) === true) {
×
148
                        $reportClassName = $nsPrefix . '\\' . $trimmedType;
×
149
                        break;
×
150
                    }
151
                }
152
            }//end if
153

154
            if ($reportClassName === '') {
×
155
                $error = "ERROR: Class file for report \"$type\" not found" . PHP_EOL;
×
156
                throw new DeepExitException($error, ExitCode::PROCESS_ERROR);
×
157
            }
158

159
            $reportClass = new $reportClassName();
×
160
            if (($reportClass instanceof Report) === false) {
×
161
                throw new RuntimeException('Class "' . $reportClassName . '" must implement the "PHP_CodeSniffer\Report" interface.');
×
162
            }
163

164
            $this->reports[$type] = [
×
165
                'output' => $output,
×
166
                'class'  => $reportClass,
×
167
            ];
168

169
            if ($output === null) {
×
170
                // Using a temp file.
171
                // This needs to be set in the constructor so that all
172
                // child procs use the same report file when running in parallel.
173
                $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
×
174
                file_put_contents($this->tmpFiles[$type], '');
×
175
            } else {
176
                file_put_contents($output, '');
×
177
            }
178
        }//end foreach
179
    }
180

181

182
    /**
183
     * Check whether a (virtual) property is set.
184
     *
185
     * @param string $name Property name.
186
     *
187
     * @return bool
188
     */
189
    public function __isset(string $name)
15✔
190
    {
191
        return ($name === 'totalFixable' || $name === 'totalFixed');
15✔
192
    }
193

194

195
    /**
196
     * Get the value of an inaccessible property.
197
     *
198
     * The properties supported via this method are both deprecated since PHP_CodeSniffer 4.0.
199
     * - For $totalFixable, use `($reporter->totalFixableErrors + $reporter->totalFixableWarnings)` instead.
200
     * - For $totalFixed, use `($reporter->totalFixedErrors + $reporter->totalFixedWarnings)` instead.
201
     *
202
     * @param string $name The name of the property.
203
     *
204
     * @return int
205
     *
206
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid.
207
     */
208
    public function __get(string $name)
15✔
209
    {
210
        if ($name === 'totalFixable') {
15✔
211
            return ($this->totalFixableErrors + $this->totalFixableWarnings);
6✔
212
        }
213

214
        if ($name === 'totalFixed') {
9✔
215
            return ($this->totalFixedErrors + $this->totalFixedWarnings);
6✔
216
        }
217

218
        throw new RuntimeException("ERROR: access requested to unknown property \"Reporter::\${$name}\"");
3✔
219
    }
220

221

222
    /**
223
     * Setting a dynamic/virtual property on this class is not allowed.
224
     *
225
     * @param string $name  Property name.
226
     * @param mixed  $value Property value.
227
     *
228
     * @return bool
229
     *
230
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
231
     */
232
    public function __set(string $name, $value)
3✔
233
    {
234
        throw new RuntimeException("ERROR: setting property \"Reporter::\${$name}\" is not allowed");
3✔
235
    }
236

237

238
    /**
239
     * Unsetting a dynamic/virtual property on this class is not allowed.
240
     *
241
     * @param string $name Property name.
242
     *
243
     * @return bool
244
     *
245
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
246
     */
247
    public function __unset(string $name)
3✔
248
    {
249
        throw new RuntimeException("ERROR: unsetting property \"Reporter::\${$name}\" is not allowed");
3✔
250
    }
251

252

253
    /**
254
     * Generates and prints final versions of all reports.
255
     *
256
     * Returns TRUE if any of the reports output content to the screen
257
     * or FALSE if all reports were silently printed to a file.
258
     *
259
     * @return bool
260
     */
UNCOV
261
    public function printReports()
×
262
    {
UNCOV
263
        $toScreen = false;
×
UNCOV
264
        foreach ($this->reports as $type => $report) {
×
UNCOV
265
            if ($report['output'] === null) {
×
266
                $toScreen = true;
×
267
            }
268

269
            $this->printReport($type);
×
270
        }
271

UNCOV
272
        return $toScreen;
×
273
    }
274

275

276
    /**
277
     * Generates and prints a single final report.
278
     *
279
     * @param string $report The report type to print.
280
     *
281
     * @return void
282
     */
UNCOV
283
    public function printReport(string $report)
×
284
    {
UNCOV
285
        $reportClass = $this->reports[$report]['class'];
×
UNCOV
286
        $reportFile  = $this->reports[$report]['output'];
×
287

UNCOV
288
        if ($reportFile !== null) {
×
289
            $filename = $reportFile;
×
UNCOV
290
            $toScreen = false;
×
291
        } else {
292
            if (isset($this->tmpFiles[$report]) === true) {
×
UNCOV
293
                $filename = $this->tmpFiles[$report];
×
294
            } else {
295
                $filename = null;
×
296
            }
297

298
            $toScreen = true;
×
299
        }
300

301
        $reportCache = '';
×
UNCOV
302
        if ($filename !== null) {
×
UNCOV
303
            $reportCache = file_get_contents($filename);
×
304
        }
305

UNCOV
306
        ob_start();
×
307
        $reportClass->generate(
×
308
            $reportCache,
×
309
            $this->totalFiles,
×
UNCOV
310
            $this->totalErrors,
×
UNCOV
311
            $this->totalWarnings,
×
312
            ($this->totalFixableErrors + $this->totalFixableWarnings),
×
313
            $this->config->showSources,
×
314
            $this->config->reportWidth,
×
315
            $this->config->interactive,
×
316
            $toScreen
×
317
        );
318
        $generatedReport = ob_get_contents();
×
319
        ob_end_clean();
×
320

321
        if ($this->config->colors !== true || $reportFile !== null) {
×
322
            $generatedReport = Common::stripColors($generatedReport);
×
323
        }
324

325
        if ($reportFile !== null) {
×
UNCOV
326
            if (PHP_CODESNIFFER_VERBOSITY > 0) {
×
327
                echo $generatedReport;
×
328
            }
329

UNCOV
330
            file_put_contents($reportFile, $generatedReport . PHP_EOL);
×
331
        } else {
332
            echo $generatedReport;
×
333
            if ($filename !== null && file_exists($filename) === true) {
×
UNCOV
334
                unlink($filename);
×
UNCOV
335
                unset($this->tmpFiles[$report]);
×
336
            }
337
        }
338
    }
339

340

341
    /**
342
     * Caches the result of a single processed file for all reports.
343
     *
344
     * The report content that is generated is appended to the output file
345
     * assigned to each report. This content may be an intermediate report format
346
     * and not reflect the final report output.
347
     *
348
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
349
     *
350
     * @return void
351
     */
UNCOV
352
    public function cacheFileReport(File $phpcsFile)
×
353
    {
UNCOV
354
        if (isset($this->config->reports) === false) {
×
355
            // This happens during unit testing, or any time someone just wants
356
            // the error data and not the printed report.
UNCOV
357
            return;
×
358
        }
359

UNCOV
360
        $reportData  = $this->prepareFileReport($phpcsFile);
×
361
        $errorsShown = false;
×
362

UNCOV
363
        foreach ($this->reports as $type => $report) {
×
364
            $reportClass = $report['class'];
×
365

UNCOV
366
            ob_start();
×
367
            $result = $reportClass->generateFileReport($reportData, $phpcsFile, $this->config->showSources, $this->config->reportWidth);
×
368
            if ($result === true) {
×
UNCOV
369
                $errorsShown = true;
×
370
            }
371

UNCOV
372
            $generatedReport = ob_get_contents();
×
373
            ob_end_clean();
×
374

375
            if ($report['output'] === null) {
×
376
                // Using a temp file.
UNCOV
377
                if (isset($this->tmpFiles[$type]) === false) {
×
378
                    // When running in interactive mode, the reporter prints the full
379
                    // report many times, which will unlink the temp file. So we need
380
                    // to create a new one if it doesn't exist.
UNCOV
381
                    $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
×
382
                    file_put_contents($this->tmpFiles[$type], '');
×
383
                }
384

UNCOV
385
                file_put_contents($this->tmpFiles[$type], $generatedReport, (FILE_APPEND | LOCK_EX));
×
386
            } else {
UNCOV
387
                file_put_contents($report['output'], $generatedReport, (FILE_APPEND | LOCK_EX));
×
388
            }//end if
389
        }//end foreach
390

UNCOV
391
        if ($errorsShown === true || PHP_CODESNIFFER_CBF === true) {
×
392
            $this->totalFiles++;
×
UNCOV
393
            $this->totalErrors   += $reportData['errors'];
×
394
            $this->totalWarnings += $reportData['warnings'];
×
395

396
            // When PHPCBF is running, we need to use the fixable error values
397
            // after the report has run and fixed what it can.
398
            $this->totalFixableErrors   += $phpcsFile->getFixableErrorCount();
×
399
            $this->totalFixableWarnings += $phpcsFile->getFixableWarningCount();
×
400
            $this->totalFixedErrors     += $phpcsFile->getFixedErrorCount();
×
401
            $this->totalFixedWarnings   += $phpcsFile->getFixedWarningCount();
×
402
        }
403
    }
404

405

406
    /**
407
     * Generate summary information to be used during report generation.
408
     *
409
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
410
     *
411
     * @return array<string, string|int|array> Prepared report data.
412
     *                                         The format of prepared data is as follows:
413
     *                                         ```
414
     *                                         array(
415
     *                                           'filename' => string The name of the current file.
416
     *                                           'errors'   => int    The number of errors seen in the current file.
417
     *                                           'warnings' => int    The number of warnings seen in the current file.
418
     *                                           'fixable'  => int    The number of fixable issues seen in the current file.
419
     *                                           'messages' => array(
420
     *                                             int <Line number> => array(
421
     *                                               int <Column number> => array(
422
     *                                                 int <Message index> => array(
423
     *                                                   'message'  => string The error/warning message.
424
     *                                                   'source'   => string The full error code for the message.
425
     *                                                   'severity' => int    The severity of the message.
426
     *                                                   'fixable'  => bool   Whether this error/warning is auto-fixable.
427
     *                                                   'type'     => string The type of message. Either 'ERROR' or 'WARNING'.
428
     *                                                 )
429
     *                                               )
430
     *                                             )
431
     *                                           )
432
     *                                         )
433
     *                                         ```
434
     */
UNCOV
435
    public function prepareFileReport(File $phpcsFile)
×
436
    {
437
        $report = [
UNCOV
438
            'filename' => Common::stripBasepath($phpcsFile->getFilename(), $this->config->basepath),
×
UNCOV
439
            'errors'   => $phpcsFile->getErrorCount(),
×
UNCOV
440
            'warnings' => $phpcsFile->getWarningCount(),
×
UNCOV
441
            'fixable'  => $phpcsFile->getFixableCount(),
×
442
            'messages' => [],
443
        ];
444

UNCOV
445
        if ($report['errors'] === 0 && $report['warnings'] === 0) {
×
446
            // Perfect score!
447
            return $report;
×
448
        }
449

UNCOV
450
        if ($this->config->recordErrors === false) {
×
UNCOV
451
            $message  = 'Errors are not being recorded but this report requires error messages. ';
×
UNCOV
452
            $message .= 'This report will not show the correct information.';
×
453
            $report['messages'][1][1] = [
×
454
                [
455
                    'message'  => $message,
×
UNCOV
456
                    'source'   => 'Internal.RecordErrors',
×
UNCOV
457
                    'severity' => 5,
×
458
                    'fixable'  => false,
459
                    'type'     => 'ERROR',
×
460
                ],
461
            ];
UNCOV
462
            return $report;
×
463
        }
464

465
        $errors = [];
×
466

467
        // Merge errors and warnings.
UNCOV
468
        foreach ($phpcsFile->getErrors() as $line => $lineErrors) {
×
UNCOV
469
            foreach ($lineErrors as $column => $colErrors) {
×
470
                $newErrors = [];
×
UNCOV
471
                foreach ($colErrors as $data) {
×
UNCOV
472
                    $newErrors[] = [
×
473
                        'message'  => $data['message'],
×
UNCOV
474
                        'source'   => $data['source'],
×
UNCOV
475
                        'severity' => $data['severity'],
×
476
                        'fixable'  => $data['fixable'],
×
477
                        'type'     => 'ERROR',
×
478
                    ];
479
                }
480

481
                $errors[$line][$column] = $newErrors;
×
482
            }
483

484
            ksort($errors[$line]);
×
485
        }//end foreach
486

UNCOV
487
        foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) {
×
UNCOV
488
            foreach ($lineWarnings as $column => $colWarnings) {
×
489
                $newWarnings = [];
×
UNCOV
490
                foreach ($colWarnings as $data) {
×
UNCOV
491
                    $newWarnings[] = [
×
492
                        'message'  => $data['message'],
×
UNCOV
493
                        'source'   => $data['source'],
×
UNCOV
494
                        'severity' => $data['severity'],
×
495
                        'fixable'  => $data['fixable'],
×
496
                        'type'     => 'WARNING',
×
497
                    ];
498
                }
499

500
                if (isset($errors[$line]) === false) {
×
501
                    $errors[$line] = [];
×
502
                }
503

504
                if (isset($errors[$line][$column]) === true) {
×
UNCOV
505
                    $errors[$line][$column] = array_merge(
×
UNCOV
506
                        $newWarnings,
×
UNCOV
507
                        $errors[$line][$column]
×
508
                    );
509
                } else {
UNCOV
510
                    $errors[$line][$column] = $newWarnings;
×
511
                }
512
            }//end foreach
513

514
            ksort($errors[$line]);
×
515
        }//end foreach
516

UNCOV
517
        ksort($errors);
×
518
        $report['messages'] = $errors;
×
UNCOV
519
        return $report;
×
520
    }
521
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc