Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Runner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Runner, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class Runner |
||
23 | { |
||
24 | |||
25 | /** |
||
26 | * The config data for the run. |
||
27 | * |
||
28 | * @var \Symplify\PHP7_CodeSniffer\Config |
||
29 | */ |
||
30 | public $config = null; |
||
31 | |||
32 | /** |
||
33 | * The ruleset used for the run. |
||
34 | * |
||
35 | * @var \Symplify\PHP7_CodeSniffer\Ruleset |
||
36 | */ |
||
37 | public $ruleset = null; |
||
38 | |||
39 | /** |
||
40 | * The reporter used for generating reports after the run. |
||
41 | * |
||
42 | * @var \Symplify\PHP7_CodeSniffer\Reporter |
||
43 | */ |
||
44 | public $reporter = null; |
||
45 | |||
46 | |||
47 | /** |
||
48 | * Run the PHPCS script. |
||
49 | * |
||
50 | * @return array |
||
51 | */ |
||
52 | public function runPHPCS() |
||
53 | { |
||
54 | Util\Timing::startTiming(); |
||
55 | |||
56 | if (defined('PHP_CodeSniffer_CBF') === false) { |
||
57 | define('PHP_CodeSniffer_CBF', false); |
||
58 | } |
||
59 | |||
60 | // Creating the Config object populates it with all required settings |
||
61 | // based on the CLI arguments provided to the script and any config |
||
62 | // values the user has set. |
||
63 | $this->config = new Config(); |
||
64 | |||
65 | // Init the run and load the rulesets to set additional config vars. |
||
66 | $this->init(); |
||
67 | |||
68 | // Print a list of sniffs in each of the supplied standards. |
||
69 | // We fudge the config here so that each standard is explained in isolation. |
||
70 | if ($this->config->explain === true) { |
||
|
|||
71 | $standards = $this->config->standards; |
||
72 | foreach ($standards as $standard) { |
||
73 | $this->config->standards = array($standard); |
||
74 | $ruleset = new Ruleset($this->config); |
||
75 | $ruleset->explain(); |
||
76 | } |
||
77 | |||
78 | exit(0); |
||
79 | } |
||
80 | |||
81 | // Generate documentation for each of the supplied standards. |
||
82 | if ($this->config->generator !== null) { |
||
83 | $standards = $this->config->standards; |
||
84 | foreach ($standards as $standard) { |
||
85 | $this->config->standards = array($standard); |
||
86 | $ruleset = new Ruleset($this->config); |
||
87 | $class = 'Symplify\PHP7_CodeSniffer\Generators\\'.$this->config->generator; |
||
88 | $generator = new $class($ruleset); |
||
89 | $generator->generate(); |
||
90 | } |
||
91 | |||
92 | exit(0); |
||
93 | } |
||
94 | |||
95 | // Other report formats don't really make sense in interactive mode |
||
96 | // so we hard-code the full report here and when outputting. |
||
97 | // We also ensure parallel processing is off because we need to do one file at a time. |
||
98 | if ($this->config->interactive === true) { |
||
99 | $this->config->reports = array('full' => null); |
||
100 | $this->config->parallel = 1; |
||
101 | } |
||
102 | |||
103 | // Disable caching if we are processing STDIN as we can't be 100% |
||
104 | // sure where the file came from or if it will change in the future. |
||
105 | if ($this->config->stdin === true) { |
||
106 | $this->config->cache = false; |
||
107 | } |
||
108 | |||
109 | $numErrors = $this->run(); |
||
110 | |||
111 | // Print all the reports for this run. |
||
112 | $toScreen = $this->reporter->printReports(); |
||
113 | |||
114 | // Only print timer output if no reports were |
||
115 | // printed to the screen so we don't put additional output |
||
116 | // in something like an XML report. If we are printing to screen, |
||
117 | // the report types would have already worked out who should |
||
118 | // print the timer info. |
||
119 | if ($this->config->interactive === false |
||
120 | && ($toScreen === false |
||
121 | || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true)) |
||
122 | ) { |
||
123 | Util\Timing::printRunTime(); |
||
124 | } |
||
125 | |||
126 | if ($numErrors === 0) { |
||
127 | exit(0); |
||
128 | } else { |
||
129 | exit(1); |
||
130 | } |
||
131 | |||
132 | }//end runPHPCS() |
||
133 | |||
134 | |||
135 | /** |
||
136 | * Run the PHPCBF script. |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | public function runPHPCBF() |
||
141 | { |
||
142 | if (defined('PHP_CodeSniffer_CBF') === false) { |
||
143 | define('PHP_CodeSniffer_CBF', true); |
||
144 | } |
||
145 | |||
146 | Util\Timing::startTiming(); |
||
147 | |||
148 | // Creating the Config object populates it with all required settings |
||
149 | // based on the CLI arguments provided to the script and any config |
||
150 | // values the user has set. |
||
151 | $this->config = new Config(); |
||
152 | |||
153 | // Init the run and load the rulesets to set additional config vars. |
||
154 | $this->init(); |
||
155 | |||
156 | // Override some of the command line settings that might break the fixes. |
||
157 | $this->config->verbosity = 0; |
||
158 | $this->config->showProgress = false; |
||
159 | $this->config->generator = null; |
||
160 | $this->config->explain = false; |
||
161 | $this->config->interactive = false; |
||
162 | $this->config->cache = false; |
||
163 | $this->config->showSources = false; |
||
164 | $this->config->recordErrors = false; |
||
165 | $this->config->reportFile = null; |
||
166 | $this->config->reports = array('cbf' => null); |
||
167 | |||
168 | // If a standard tries to set command line arguments itself, some |
||
169 | // may be blocked because PHPCBF is running, so stop the script |
||
170 | // dying if any are found. |
||
171 | $this->config->dieOnUnknownArg = false; |
||
172 | |||
173 | $numErrors = $this->run(); |
||
174 | $this->reporter->printReports(); |
||
175 | |||
176 | echo PHP_EOL; |
||
177 | Util\Timing::printRunTime(); |
||
178 | |||
179 | // We can't tell exactly how many errors were fixed, but |
||
180 | // we know how many errors were found. |
||
181 | exit($numErrors); |
||
182 | |||
183 | }//end runPHPCBF() |
||
184 | |||
185 | |||
186 | /** |
||
187 | * Init the rulesets and other high-level settings. |
||
188 | * |
||
189 | * @return void |
||
190 | */ |
||
191 | private function init() |
||
192 | { |
||
193 | // Ensure this option is enabled or else line endings will not always |
||
194 | // be detected properly for files created on a Mac with the /r line ending. |
||
195 | ini_set('auto_detect_line_endings', true); |
||
196 | |||
197 | // Check that the standards are valid. |
||
198 | foreach ($this->config->standards as $standard) { |
||
199 | if (Util\Standards::isInstalledStandard($standard) === false) { |
||
200 | // They didn't select a valid coding standard, so help them |
||
201 | // out by letting them know which standards are installed. |
||
202 | echo 'ERROR: the "'.$standard.'" coding standard is not installed. '; |
||
203 | Util\Standards::printInstalledStandards(); |
||
204 | exit(2); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | // Saves passing the Config object into other objects that only need |
||
209 | // the verbostity flag for deubg output. |
||
210 | if (defined('PHP_CodeSniffer_VERBOSITY') === false) { |
||
211 | define('PHP_CodeSniffer_VERBOSITY', $this->config->verbosity); |
||
212 | } |
||
213 | |||
214 | // Create this class so it is autoloaded and sets up a bunch |
||
215 | // of Symplify\PHP7_CodeSniffer-specific token type constants. |
||
216 | $tokens = new Util\Tokens(); |
||
217 | |||
218 | // The ruleset contains all the information about how the files |
||
219 | // should be checked and/or fixed. |
||
220 | $this->ruleset = new Ruleset($this->config); |
||
221 | |||
222 | }//end init() |
||
223 | |||
224 | |||
225 | /** |
||
226 | * Performs the run. |
||
227 | * |
||
228 | * @return int The number of errors and warnings found. |
||
229 | */ |
||
230 | private function run() |
||
231 | { |
||
232 | // The class that manages all reporters for the run. |
||
233 | $this->reporter = new Reporter($this->config); |
||
234 | |||
235 | // Include bootstrap files. |
||
236 | foreach ($this->config->bootstrap as $bootstrap) { |
||
237 | include $bootstrap; |
||
238 | } |
||
239 | |||
240 | if ($this->config->stdin === true) { |
||
241 | $fileContents = $this->config->stdinContent; |
||
242 | if ($fileContents === null) { |
||
243 | $handle = fopen('php://stdin', 'r'); |
||
244 | stream_set_blocking($handle, true); |
||
245 | $fileContents = stream_get_contents($handle); |
||
246 | fclose($handle); |
||
247 | } |
||
248 | |||
249 | $todo = new FileList($this->config, $this->ruleset); |
||
250 | $dummy = new DummyFile($fileContents, $this->ruleset, $this->config); |
||
251 | $todo->addFile($dummy->path, $dummy); |
||
252 | |||
253 | $numFiles = 1; |
||
254 | } else { |
||
255 | if (empty($this->config->files) === true) { |
||
256 | echo 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL; |
||
257 | $this->config->printUsage(); |
||
258 | exit(0); |
||
259 | } |
||
260 | |||
261 | if (PHP_CodeSniffer_VERBOSITY > 0) { |
||
262 | echo 'Creating file list... '; |
||
263 | } |
||
264 | |||
265 | $todo = new FileList($this->config, $this->ruleset); |
||
266 | $numFiles = count($todo); |
||
267 | |||
268 | if (PHP_CodeSniffer_VERBOSITY > 0) { |
||
269 | echo "DONE ($numFiles files in queue)".PHP_EOL; |
||
270 | } |
||
271 | |||
272 | if ($this->config->cache === true) { |
||
273 | if (PHP_CodeSniffer_VERBOSITY > 0) { |
||
274 | echo 'Loading cache... '; |
||
275 | } |
||
276 | |||
277 | Cache::load($this->ruleset, $this->config); |
||
278 | |||
279 | if (PHP_CodeSniffer_VERBOSITY > 0) { |
||
280 | $size = Cache::getSize(); |
||
281 | echo "DONE ($size files in cache)".PHP_EOL; |
||
282 | } |
||
283 | } |
||
284 | }//end if |
||
285 | |||
286 | $numProcessed = 0; |
||
287 | $dots = 0; |
||
288 | $maxLength = strlen($numFiles); |
||
289 | $lastDir = ''; |
||
290 | $childProcs = array(); |
||
291 | |||
292 | // Turn all sniff errors into exceptions. |
||
293 | set_error_handler(array($this, 'handleErrors')); |
||
294 | |||
295 | // If verbosity is too high, turn off parallelism so the |
||
296 | // debug output is clean. |
||
297 | if (PHP_CodeSniffer_VERBOSITY > 1) { |
||
298 | $this->config->parallel = 1; |
||
299 | } |
||
300 | |||
301 | if ($this->config->parallel === 1) { |
||
302 | // Running normally. |
||
303 | foreach ($todo as $path => $file) { |
||
304 | $currDir = dirname($path); |
||
305 | View Code Duplication | if ($lastDir !== $currDir) { |
|
306 | if (PHP_CodeSniffer_VERBOSITY > 0 || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false)) { |
||
307 | echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL; |
||
308 | } |
||
309 | |||
310 | $lastDir = $currDir; |
||
311 | } |
||
312 | |||
313 | $this->processFile($file); |
||
314 | |||
315 | $numProcessed++; |
||
316 | |||
317 | if (PHP_CodeSniffer_VERBOSITY > 0 |
||
318 | || $this->config->interactive === true |
||
319 | || $this->config->showProgress === false |
||
320 | ) { |
||
321 | continue; |
||
322 | } |
||
323 | |||
324 | // Show progress information. |
||
325 | if ($file->ignored === true) { |
||
326 | echo 'S'; |
||
327 | } else { |
||
328 | $errors = $file->getErrorCount(); |
||
329 | $warnings = $file->getWarningCount(); |
||
330 | View Code Duplication | if ($errors > 0) { |
|
331 | if ($this->config->colors === true) { |
||
332 | echo "\033[31m"; |
||
333 | } |
||
334 | |||
335 | echo 'E'; |
||
336 | } else if ($warnings > 0) { |
||
337 | if ($this->config->colors === true) { |
||
338 | echo "\033[33m"; |
||
339 | } |
||
340 | |||
341 | echo 'W'; |
||
342 | } else { |
||
343 | echo '.'; |
||
344 | } |
||
345 | |||
346 | if ($this->config->colors === true) { |
||
347 | echo "\033[0m"; |
||
348 | } |
||
349 | }//end if |
||
350 | |||
351 | $dots++; |
||
352 | View Code Duplication | if ($dots === 60) { |
|
353 | $padding = ($maxLength - strlen($numProcessed)); |
||
354 | echo str_repeat(' ', $padding); |
||
355 | $percent = round(($numProcessed / $numFiles) * 100); |
||
356 | echo " $numProcessed / $numFiles ($percent%)".PHP_EOL; |
||
357 | $dots = 0; |
||
358 | } |
||
359 | }//end foreach |
||
360 | } else { |
||
361 | // Batching and forking. |
||
362 | $numFiles = count($todo); |
||
363 | $numPerBatch = ceil($numFiles / $this->config->parallel); |
||
364 | |||
365 | for ($batch = 0; $batch < $this->config->parallel; $batch++) { |
||
366 | $startAt = ($batch * $numPerBatch); |
||
367 | if ($startAt >= $numFiles) { |
||
368 | break; |
||
369 | } |
||
370 | |||
371 | $endAt = ($startAt + $numPerBatch); |
||
372 | if ($endAt > $numFiles) { |
||
373 | $endAt = $numFiles; |
||
374 | } |
||
375 | |||
376 | $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child'); |
||
377 | $pid = pcntl_fork(); |
||
378 | if ($pid === -1) { |
||
379 | throw new RuntimeException('Failed to create child process'); |
||
380 | } else if ($pid !== 0) { |
||
381 | $childProcs[] = array( |
||
382 | 'pid' => $pid, |
||
383 | 'out' => $childOutFilename, |
||
384 | ); |
||
385 | } else { |
||
386 | // Move forward to the start of the batch. |
||
387 | $todo->rewind(); |
||
388 | for ($i = 0; $i < $startAt; $i++) { |
||
389 | $todo->next(); |
||
390 | } |
||
391 | |||
392 | // Reset the reporter to make sure only figures from this |
||
393 | // file batch are recorded. |
||
394 | $this->reporter->totalFiles = 0; |
||
395 | $this->reporter->totalErrors = 0; |
||
396 | $this->reporter->totalWarnings = 0; |
||
397 | $this->reporter->totalFixable = 0; |
||
398 | |||
399 | // Process the files. |
||
400 | $pathsProcessed = array(); |
||
401 | ob_start(); |
||
402 | for ($i = $startAt; $i < $endAt; $i++) { |
||
403 | $path = $todo->key(); |
||
404 | $file = $todo->current(); |
||
405 | |||
406 | $currDir = dirname($path); |
||
407 | View Code Duplication | if ($lastDir !== $currDir) { |
|
408 | if (PHP_CodeSniffer_VERBOSITY > 0 || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false)) { |
||
409 | echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL; |
||
410 | } |
||
411 | |||
412 | $lastDir = $currDir; |
||
413 | } |
||
414 | |||
415 | $this->processFile($file); |
||
416 | |||
417 | $pathsProcessed[] = $path; |
||
418 | $todo->next(); |
||
419 | } |
||
420 | |||
421 | $debugOutput = ob_get_contents(); |
||
422 | ob_end_clean(); |
||
423 | |||
424 | // Write information about the run to the filesystem |
||
425 | // so it can be picked up by the main process. |
||
426 | $childOutput = array( |
||
427 | 'totalFiles' => $this->reporter->totalFiles, |
||
428 | 'totalErrors' => $this->reporter->totalErrors, |
||
429 | 'totalWarnings' => $this->reporter->totalWarnings, |
||
430 | 'totalFixable' => $this->reporter->totalFixable, |
||
431 | ); |
||
432 | |||
433 | $output = '<'.'?php'."\n".' $childOutput = '; |
||
434 | $output .= var_export($childOutput, true); |
||
435 | $output .= ";\n\$debugOutput = "; |
||
436 | $output .= var_export($debugOutput, true); |
||
437 | |||
438 | if ($this->config->cache === true) { |
||
439 | $childCache = array(); |
||
440 | foreach ($pathsProcessed as $path) { |
||
441 | $childCache[$path] = Cache::get($path); |
||
442 | } |
||
443 | |||
444 | $output .= ";\n\$childCache = "; |
||
445 | $output .= var_export($childCache, true); |
||
446 | } |
||
447 | |||
448 | $output .= ";\n?".'>'; |
||
449 | file_put_contents($childOutFilename, $output); |
||
450 | exit($pid); |
||
451 | }//end if |
||
452 | }//end for |
||
453 | |||
454 | $this->processChildProcs($childProcs); |
||
455 | }//end if |
||
456 | |||
457 | restore_error_handler(); |
||
458 | |||
459 | if (PHP_CodeSniffer_VERBOSITY === 0 |
||
460 | && $this->config->interactive === false |
||
461 | && $this->config->showProgress === true |
||
462 | ) { |
||
463 | echo PHP_EOL.PHP_EOL; |
||
464 | } |
||
465 | |||
466 | if ($this->config->cache === true) { |
||
467 | Cache::save(); |
||
468 | } |
||
469 | |||
470 | $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit'); |
||
471 | $ignoreErrors = Config::getConfigData('ignore_errors_on_exit'); |
||
472 | |||
473 | $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings); |
||
474 | if ($ignoreErrors !== null) { |
||
475 | $ignoreErrors = (bool) $ignoreErrors; |
||
476 | if ($ignoreErrors === true) { |
||
477 | $return -= $this->reporter->totalErrors; |
||
478 | } |
||
479 | } |
||
480 | |||
481 | if ($ignoreWarnings !== null) { |
||
482 | $ignoreWarnings = (bool) $ignoreWarnings; |
||
483 | if ($ignoreWarnings === true) { |
||
484 | $return -= $this->reporter->totalWarnings; |
||
485 | } |
||
486 | } |
||
487 | |||
488 | return $return; |
||
489 | |||
490 | }//end run() |
||
491 | |||
492 | |||
493 | /** |
||
494 | * Converts all PHP errors into exceptions. |
||
495 | * |
||
496 | * This method forces a sniff to stop processing if it is not |
||
497 | * able to handle a specific piece of code, instead of continuing |
||
498 | * and potentially getting into a loop. |
||
499 | * |
||
500 | * @param int $code The level of error raised. |
||
501 | * @param string $message The error message. |
||
502 | * @param string $file The path of the file that raised the error. |
||
503 | * @param int $line The line number the error was raised at. |
||
504 | * |
||
505 | * @return void |
||
506 | */ |
||
507 | public function handleErrors($code, $message, $file, $line) |
||
512 | |||
513 | |||
514 | /** |
||
515 | * Processes a single file, including checking and fixing. |
||
516 | * |
||
517 | * @param \Symplify\PHP7_CodeSniffer\Files\File $file The file to be processed. |
||
518 | * |
||
519 | * @return void |
||
520 | */ |
||
521 | private function processFile($file) |
||
522 | { |
||
523 | if (PHP_CodeSniffer_VERBOSITY > 0 || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false)) { |
||
524 | $startTime = microtime(true); |
||
525 | echo 'Processing '.basename($file->path).' '; |
||
526 | if (PHP_CodeSniffer_VERBOSITY > 1) { |
||
527 | echo PHP_EOL; |
||
528 | } |
||
529 | } |
||
530 | |||
531 | try { |
||
532 | $file->process(); |
||
533 | |||
534 | if (PHP_CodeSniffer_VERBOSITY > 0 |
||
535 | || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false) |
||
536 | ) { |
||
537 | $timeTaken = ((microtime(true) - $startTime) * 1000); |
||
538 | if ($timeTaken < 1000) { |
||
539 | $timeTaken = round($timeTaken); |
||
540 | echo "DONE in {$timeTaken}ms"; |
||
541 | } else { |
||
542 | $timeTaken = round(($timeTaken / 1000), 2); |
||
543 | echo "DONE in $timeTaken secs"; |
||
544 | } |
||
545 | |||
546 | if (PHP_CodeSniffer_CBF === true) { |
||
547 | $errors = $file->getFixableCount(); |
||
548 | echo " ($errors fixable violations)".PHP_EOL; |
||
549 | } else { |
||
550 | $errors = $file->getErrorCount(); |
||
551 | $warnings = $file->getWarningCount(); |
||
552 | echo " ($errors errors, $warnings warnings)".PHP_EOL; |
||
553 | } |
||
554 | } |
||
555 | } catch (\Exception $e) { |
||
556 | $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage(); |
||
557 | $file->addErrorOnLine($error, 1, 'Internal.Exception'); |
||
558 | }//end try |
||
559 | |||
560 | $this->reporter->cacheFileReport($file, $this->config); |
||
561 | |||
562 | // Clean up the file to save (a lot of) memory. |
||
563 | $file->cleanUp(); |
||
564 | |||
565 | if ($this->config->interactive === true) { |
||
566 | /* |
||
567 | Running interactively. |
||
568 | Print the error report for the current file and then wait for user input. |
||
569 | */ |
||
570 | |||
571 | // Get current violations and then clear the list to make sure |
||
572 | // we only print violations for a single file each time. |
||
573 | $numErrors = null; |
||
574 | while ($numErrors !== 0) { |
||
575 | $numErrors = ($file->getErrorCount() + $file->getWarningCount()); |
||
576 | if ($numErrors === 0) { |
||
577 | continue; |
||
578 | } |
||
579 | |||
580 | $this->reporter->printReport('full'); |
||
581 | |||
582 | echo '<ENTER> to recheck, [s] to skip or [q] to quit : '; |
||
583 | $input = fgets(STDIN); |
||
584 | $input = trim($input); |
||
585 | |||
586 | switch ($input) { |
||
587 | case 's': |
||
588 | break(2); |
||
589 | case 'q': |
||
590 | exit(0); |
||
591 | default: |
||
592 | // Repopulate the sniffs because some of them save their state |
||
593 | // and only clear it when the file changes, but we are rechecking |
||
594 | // the same file. |
||
595 | $file->ruleset->populateTokenListeners(); |
||
596 | $file->reloadContent(); |
||
597 | $file->process(); |
||
598 | $this->reporter->cacheFileReport($file, $this->config); |
||
599 | break; |
||
600 | } |
||
601 | }//end while |
||
602 | }//end if |
||
603 | |||
604 | }//end processFile() |
||
605 | |||
606 | |||
607 | /** |
||
608 | * Waits for child processes to complete and cleans up after them. |
||
609 | * |
||
610 | * The reporting information returned by each child process is merged |
||
611 | * into the main reporter class. |
||
612 | * |
||
613 | * @param array $childProcs An array of child processes to wait for. |
||
614 | * |
||
615 | * @return void |
||
616 | */ |
||
617 | private function processChildProcs($childProcs) |
||
692 | |||
693 | |||
694 | }//end class |
||
695 |
Since your code implements the magic getter
_get
, this function will be called for any read access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.If the property has read access only, you can use the @property-read annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.