@@ -41,6 +41,11 @@ class CheckerCommand extends Command
4141 */
4242 protected $ warnings = [];
4343
44+ /**
45+ * @var array
46+ */
47+ protected $ infos = [];
48+
4449 /**
4550 * @var array
4651 */
@@ -61,11 +66,26 @@ class CheckerCommand extends Command
6166 */
6267 protected $ skipSignatures = false ;
6368
69+ /**
70+ * @var bool
71+ */
72+ protected $ onlySignatures = false ;
73+
6474 /**
6575 * @var bool
6676 */
6777 protected $ fromStdin = false ;
6878
79+ /**
80+ * @var string
81+ */
82+ protected $ cacheFile ;
83+
84+ /**
85+ * @var array
86+ */
87+ protected $ cache ;
88+
6989 /**
7090 * @var OutputInterface
7191 */
@@ -87,11 +107,13 @@ protected function configure()
87107 ->addOption ('skip-classes ' , null , InputOption::VALUE_NONE , 'Don \'t check classes for docblocks. ' )
88108 ->addOption ('skip-methods ' , null , InputOption::VALUE_NONE , 'Don \'t check methods for docblocks. ' )
89109 ->addOption ('skip-signatures ' , null , InputOption::VALUE_NONE , 'Don \'t check docblocks against method signatures. ' )
110+ ->addOption ('only-signatures ' , null , InputOption::VALUE_NONE , 'Ignore missing docblocks where method doesn \'t have parameters or return type. ' )
90111 ->addOption ('json ' , 'j ' , InputOption::VALUE_NONE , 'Output JSON instead of a log. ' )
91112 ->addOption ('files-per-line ' , 'l ' , InputOption::VALUE_REQUIRED , 'Number of files per line in progress ' , 50 )
92113 ->addOption ('fail-on-warnings ' , 'w ' , InputOption::VALUE_NONE , 'Consider the check failed if any warnings are produced. ' )
93114 ->addOption ('info-only ' , 'i ' , InputOption::VALUE_NONE , 'Information-only mode, just show summary. ' )
94- ->addOption ('from-stdin ' , null , InputOption::VALUE_NONE , 'Use list of files from stdin (e.g. git diff) ' );
115+ ->addOption ('from-stdin ' , null , InputOption::VALUE_NONE , 'Use list of files from stdin (e.g. git diff) ' )
116+ ->addOption ('cache-file ' , null , InputOption::VALUE_REQUIRED , 'Cache analysis of files based on filemtime. ' );
95117 }
96118
97119 /**
@@ -111,7 +133,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
111133 $ this ->skipClasses = $ input ->getOption ('skip-classes ' );
112134 $ this ->skipMethods = $ input ->getOption ('skip-methods ' );
113135 $ this ->skipSignatures = $ input ->getOption ('skip-signatures ' );
136+ $ this ->onlySignatures = $ input ->getOption ('only-signatures ' );
114137 $ this ->fromStdin = $ input ->getOption ('from-stdin ' );
138+ $ this ->cacheFile = $ input ->getOption ('cache-file ' );
115139 $ failOnWarnings = $ input ->getOption ('fail-on-warnings ' );
116140 $ startTime = microtime (true );
117141
@@ -125,6 +149,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
125149 $ this ->basePath .= '/ ' ;
126150 }
127151
152+ // Fix conflicting options:
153+ if ($ this ->onlySignatures ) {
154+ $ this ->skipSignatures = false ;
155+ }
156+
157+ // Load cache from file if set:
158+ if (!empty ($ this ->cacheFile ) && file_exists ($ this ->cacheFile )) {
159+ $ this ->cache = json_decode (file_get_contents ($ this ->cacheFile ), true );
160+ }
161+
128162 // Get files to check:
129163 $ files = [];
130164
@@ -182,6 +216,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
182216 $ this ->output ->write ('<info> ' . number_format ($ this ->passed ) . ' Passed</info> ' );
183217 $ this ->output ->write (' / <fg=red> ' .number_format (count ($ this ->errors )).' Errors</> ' );
184218 $ this ->output ->write (' / <fg=yellow> ' .number_format (count ($ this ->warnings )).' Warnings</> ' );
219+ $ this ->output ->write (' / <fg=blue> ' .number_format (count ($ this ->infos )).' Info</> ' );
185220
186221 $ this ->output ->writeln ('' );
187222
@@ -204,6 +239,25 @@ protected function execute(InputInterface $input, OutputInterface $output)
204239 }
205240 }
206241
242+ if (count ($ this ->infos ) && !$ input ->getOption ('info-only ' )) {
243+ $ this ->output ->writeln ('' );
244+ $ this ->output ->writeln ('' );
245+
246+ foreach ($ this ->infos as $ info ) {
247+ $ this ->output ->write ('<fg=blue>INFO </> ' . $ info ['file ' ] . ': ' . $ info ['line ' ] . ' - ' );
248+
249+ if ($ info ['type ' ] == 'class ' ) {
250+ $ this ->output ->write ('Class <info> ' . $ info ['class ' ] . '</info> is missing a docblock. ' );
251+ }
252+
253+ if ($ info ['type ' ] == 'method ' ) {
254+ $ this ->output ->write ('Method <info> ' . $ info ['method ' ] . '</info> is missing a docblock. ' );
255+ }
256+
257+ $ this ->output ->writeln ('' );
258+ }
259+ }
260+
207261 if (count ($ this ->warnings ) && !$ input ->getOption ('info-only ' )) {
208262 foreach ($ this ->warnings as $ error ) {
209263 $ this ->output ->write ('<fg=yellow>WARNING </> ' );
@@ -236,6 +290,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
236290 print json_encode (array_merge ($ this ->errors , $ this ->warnings ));
237291 }
238292
293+
294+ // Write to cache file:
295+ if (!empty ($ this ->cacheFile )) {
296+ @file_put_contents ($ this ->cacheFile , json_encode ($ this ->cache ));
297+ }
298+
239299 return count ($ this ->errors ) || ($ failOnWarnings && count ($ this ->warnings )) ? 1 : 0 ;
240300 }
241301
@@ -301,22 +361,36 @@ protected function processStdin(array &$worklist = [])
301361
302362 /**
303363 * Check a specific PHP file for errors.
304- * @param string $file
364+ * @param string $fileName
305365 * @return array
306366 */
307- protected function processFile ($ file )
367+ protected function processFile ($ fileName )
308368 {
309369 $ errors = false ;
310370 $ warnings = false ;
311- $ processor = new FileProcessor ($ this ->basePath . $ file );
371+ $ fullPath = $ this ->basePath . $ fileName ;
372+
373+ if (empty ($ this ->cache [$ fullPath ]) || filemtime ($ fullPath ) > $ this ->cache [$ fullPath ]['mtime ' ]) {
374+ $ processor = new FileProcessor ($ fullPath );
375+
376+ $ file = [];
377+ $ file ['mtime ' ] = filemtime ($ fullPath );
378+ $ file ['classes ' ] = $ processor ->getClasses ();
379+ $ file ['methods ' ] = $ processor ->getMethods ();
380+
381+ $ this ->cache [$ fullPath ] = $ file ;
382+
383+ }
384+
385+ $ file = $ this ->cache [$ fullPath ];
312386
313387 if (!$ this ->skipClasses ) {
314- foreach ($ processor -> getClasses () as $ name => $ class ) {
388+ foreach ($ file [ ' classes ' ] as $ name => $ class ) {
315389 if (is_null ($ class ['docblock ' ])) {
316390 $ errors = true ;
317391 $ this ->errors [] = [
318392 'type ' => 'class ' ,
319- 'file ' => $ file ,
393+ 'file ' => $ fileName ,
320394 'class ' => $ name ,
321395 'line ' => $ class ['line ' ],
322396 ];
@@ -325,22 +399,41 @@ protected function processFile($file)
325399 }
326400
327401 if (!$ this ->skipMethods ) {
328- foreach ($ processor ->getMethods () as $ name => $ method ) {
402+ foreach ($ file ['methods ' ] as $ name => $ method ) {
403+ $ treatAsError = true ;
404+
405+ if ($ this ->onlySignatures ) {
406+ if ((empty ($ method ['params ' ]) || 0 === count ($ method ['params ' ])) && false === $ method ['has_return ' ]) {
407+ $ treatAsError = false ;
408+ }
409+ }
410+
329411 if (is_null ($ method ['docblock ' ])) {
330- $ errors = true ;
331- $ this ->errors [] = [
332- 'type ' => 'method ' ,
333- 'file ' => $ file ,
334- 'class ' => $ name ,
335- 'method ' => $ name ,
336- 'line ' => $ method ['line ' ],
337- ];
412+ if (true === $ treatAsError ) {
413+ $ errors = true ;
414+
415+ $ this ->errors [] = [
416+ 'type ' => 'method ' ,
417+ 'file ' => $ fileName ,
418+ 'class ' => $ name ,
419+ 'method ' => $ name ,
420+ 'line ' => $ method ['line ' ],
421+ ];
422+ } else {
423+ $ this ->infos [] = [
424+ 'type ' => 'method ' ,
425+ 'file ' => $ fileName ,
426+ 'class ' => $ name ,
427+ 'method ' => $ name ,
428+ 'line ' => $ method ['line ' ],
429+ ];
430+ }
338431 }
339432 }
340433 }
341434
342435 if (!$ this ->skipSignatures ) {
343- foreach ($ processor -> getMethods () as $ name => $ method ) {
436+ foreach ($ file [ ' methods ' ] as $ name => $ method ) {
344437 // If the docblock is inherited, we can't check for params and return types:
345438 if (isset ($ method ['docblock ' ]['inherit ' ]) && $ method ['docblock ' ]['inherit ' ]) {
346439 continue ;
@@ -352,7 +445,7 @@ protected function processFile($file)
352445 $ warnings = true ;
353446 $ this ->warnings [] = [
354447 'type ' => 'param-missing ' ,
355- 'file ' => $ file ,
448+ 'file ' => $ fileName ,
356449 'class ' => $ name ,
357450 'method ' => $ name ,
358451 'line ' => $ method ['line ' ],
@@ -372,7 +465,7 @@ protected function processFile($file)
372465 $ warnings = true ;
373466 $ this ->warnings [] = [
374467 'type ' => 'param-mismatch ' ,
375- 'file ' => $ file ,
468+ 'file ' => $ fileName ,
376469 'class ' => $ name ,
377470 'method ' => $ name ,
378471 'line ' => $ method ['line ' ],
@@ -392,7 +485,7 @@ protected function processFile($file)
392485 $ warnings = true ;
393486 $ this ->warnings [] = [
394487 'type ' => 'return-missing ' ,
395- 'file ' => $ file ,
488+ 'file ' => $ fileName ,
396489 'class ' => $ name ,
397490 'method ' => $ name ,
398491 'line ' => $ method ['line ' ],
@@ -404,7 +497,7 @@ protected function processFile($file)
404497 $ warnings = true ;
405498 $ this ->warnings [] = [
406499 'type ' => 'return-mismatch ' ,
407- 'file ' => $ file ,
500+ 'file ' => $ fileName ,
408501 'class ' => $ name ,
409502 'method ' => $ name ,
410503 'line ' => $ method ['line ' ],
@@ -419,7 +512,7 @@ protected function processFile($file)
419512 $ warnings = true ;
420513 $ this ->warnings [] = [
421514 'type ' => 'return-mismatch ' ,
422- 'file ' => $ file ,
515+ 'file ' => $ fileName ,
423516 'class ' => $ name ,
424517 'method ' => $ name ,
425518 'line ' => $ method ['line ' ],
0 commit comments