loadCheckList($include_manual); } /** * @throws RuntimeException */ public function runSingleCheck(string $item_name, int $fix_policy = FIX_POLICY_DIE): void { foreach ($this->check_list as $item) { if ($item->item_name === $item_name) { $this->check_list = [$item]; break; } } $this->runCheck($fix_policy); } /** * @throws RuntimeException */ public function runCheck(int $fix_policy = FIX_POLICY_DIE): void { foreach ($this->check_list as $item) { if ($item->limit_os !== null && $item->limit_os !== PHP_OS_FAMILY) { continue; } $this->output->write('Checking ' . $item->item_name . ' ... '); $result = call_user_func($item->callback); if ($result === null) { $this->output->writeln('skipped'); } elseif ($result instanceof CheckResult) { if ($result->isOK()) { $this->output->writeln($result->getMessage() ?? 'ok'); continue; } // Failed $this->output->writeln('' . $result->getMessage() . ''); switch ($fix_policy) { case FIX_POLICY_DIE: throw new RuntimeException('Some check items can not be fixed !'); case FIX_POLICY_PROMPT: if ($result->getFixItem() !== '') { $helper = new QuestionHelper(); $question = new ConfirmationQuestion('Do you want to fix it? [Y/n] ', true); if ($helper->ask($this->input, $this->output, $question)) { $this->emitFix($result); } else { throw new RuntimeException('You cancelled fix'); } } else { throw new RuntimeException('Some check items can not be fixed !'); } break; case FIX_POLICY_AUTOFIX: if ($result->getFixItem() !== '') { $this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...'); $this->emitFix($result); } else { throw new RuntimeException('Some check items can not be fixed !'); } break; } } } } /** * Load Doctor check item list * * @throws \ReflectionException * @throws RuntimeException * @throws FileSystemException */ private function loadCheckList(bool $include_manual = false): void { foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\\doctor\\item') as $class) { $ref = new \ReflectionClass($class); foreach ($ref->getMethods() as $method) { $attr = $method->getAttributes(); foreach ($attr as $a) { if (is_a($a->getName(), AsCheckItem::class, true)) { /** @var AsCheckItem $instance */ $instance = $a->newInstance(); if (!$include_manual && $instance->manual) { continue; } $instance->callback = [new $class(), $method->getName()]; $this->check_list[] = $instance; } elseif (is_a($a->getName(), AsFixItem::class, true)) { /** @var AsFixItem $instance */ $instance = $a->newInstance(); // Redundant fix item if (isset($this->fix_map[$instance->name])) { throw new RuntimeException('Redundant doctor fix item: ' . $instance->name); } $this->fix_map[$instance->name] = [new $class(), $method->getName()]; } } } } // sort check list by level usort($this->check_list, fn ($a, $b) => $a->level > $b->level ? -1 : ($a->level == $b->level ? 0 : 1)); } /** * @throws RuntimeException */ private function emitFix(CheckResult $result): void { pcntl_signal(SIGINT, function () { $this->output->writeln('You cancelled fix'); }); $fix = $this->fix_map[$result->getFixItem()]; $fix_result = call_user_func($fix, ...$result->getFixParams()); pcntl_signal(SIGINT, SIG_IGN); if ($fix_result) { $this->output->writeln('Fix done'); } else { $this->output->writeln('Fix failed'); throw new RuntimeException('Some check item are not fixed'); } } }