*/ class DocBloxErrorChecker extends \Asika\SimpleConsole\Console { protected $helpOptions = ['h', 'help', '?']; /** @var App */ private $app; public function __construct(App $app, array $argv = null) { parent::__construct($argv); $this->app = $app; } protected function getHelp() { $help = <<getOption('v')) { $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } if (count($this->args) > 0) { throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); } if (!$this->commandExists('docblox')) { throw new \RuntimeException('DocBlox isn\'t available.'); } $dir = $this->app->getBasePath(); //stack for dirs to search $dirstack = []; //list of source files $filelist = []; //loop over all files in $dir while ($dh = opendir($dir)) { while ($file = readdir($dh)) { if (is_dir($dir . "/" . $file)) { //add to directory stack if (strpos($file, '.') !== 0) { array_push($dirstack, $dir . "/" . $file); $this->out('dir ' . $dir . '/' . $file); } } else { //test if it is a source file and add to filelist if (substr($file, strlen($file) - 4) == ".php") { array_push($filelist, $dir . "/" . $file); $this->out($dir . '/' . $file); } } } //look at the next dir $dir = array_pop($dirstack); } //check the entire set if ($this->runs($filelist)) { throw new \RuntimeException("I can not detect a problem."); } //check half of the set and discard if that half is okay $res = $filelist; $i = count($res); do { $this->out($i . '/' . count($filelist) . ' elements remaining.'); $res = $this->reduce($res, count($res) / 2); shuffle($res); $i = count($res); } while (count($res) < $i); //check one file after another $needed = []; while (count($res) != 0) { $file = array_pop($res); if ($this->runs(array_merge($res, $needed))) { $this->out('needs: ' . $file . ' and file count ' . count($needed)); array_push($needed, $file); } } $this->out('Smallest Set is: ' . $this->namesList($needed) . ' with ' . count($needed) . ' files. '); return 0; } private function commandExists($command) { $prefix = strpos(strtolower(PHP_OS),'win') > -1 ? 'where' : 'which'; exec("{$prefix} {$command}", $output, $returnVal); return $returnVal === 0; } /** * This function generates a comma separated list of file names. * * @param array $fileset Set of file names * * @return string comma-separated list of the file names */ private function namesList($fileset) { return implode(',', $fileset); } /** * This functions runs phpdoc on the provided list of files * * @param array $fileset Set of filenames * * @return bool true, if that set can be built */ private function runs($fileset) { $fsParam = $this->namesList($fileset); $this->exec('docblox -t phpdoc_out -f ' . $fsParam); if (file_exists("phpdoc_out/index.html")) { $this->out('Subset ' . $fsParam . ' is okay.'); $this->exec('rm -r phpdoc_out'); return true; } else { $this->out('Subset ' . $fsParam . ' failed.'); return false; } } /** * This functions cuts down a fileset by removing files until it finally works. * it was meant to be recursive, but php's maximum stack size is to small. So it just simulates recursion. * * In that version, it does not necessarily generate the smallest set, because it may not alter the elements order enough. * * @param array $fileset set of filenames * @param int $ps number of files in subsets * * @return array a part of $fileset, that crashes */ private function reduce($fileset, $ps) { //split array... $parts = array_chunk($fileset, $ps); //filter working subsets... $parts = array_filter($parts, [$this, 'runs']); //melt remaining parts together if (is_array($parts)) { return array_reduce($parts, "array_merge", []); } return []; } }