diff --git a/src/SPC/command/DoctorCommand.php b/src/SPC/command/DoctorCommand.php index 74fc48b8..b184fa21 100644 --- a/src/SPC/command/DoctorCommand.php +++ b/src/SPC/command/DoctorCommand.php @@ -5,7 +5,11 @@ declare(strict_types=1); namespace SPC\command; use SPC\doctor\CheckListHandler; +use SPC\doctor\CheckResult; +use SPC\exception\RuntimeException; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Question\ConfirmationQuestion; #[AsCommand('doctor', 'Diagnose whether the current environment can compile normally')] class DoctorCommand extends BaseCommand @@ -18,14 +22,65 @@ class DoctorCommand extends BaseCommand public function handle(): int { try { - $checker = new CheckListHandler($this->input, $this->output); - $checker->runCheck($this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT); + $checker = new CheckListHandler(); + + $fix_policy = $this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT; + foreach ($checker->runChecks() as $check) { + if ($check->limit_os !== null && $check->limit_os !== PHP_OS_FAMILY) { + continue; + } + + $this->output->write('Checking ' . $check->item_name . ' ... '); + + $result = call_user_func($check->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)) { + $checker->emitFix($this->output, $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() . ' ...'); + $checker->emitFix($this->output, $result); + } else { + throw new RuntimeException('Some check items can not be fixed !'); + } + break; + } + } + } + $this->output->writeln('Doctor check complete !'); } catch (\Throwable $e) { $this->output->writeln('' . $e->getMessage() . ''); + pcntl_signal(SIGINT, SIG_IGN); + return static::FAILURE; } + return static::SUCCESS; } } diff --git a/src/SPC/doctor/CheckListHandler.php b/src/SPC/doctor/CheckListHandler.php index 414c89f9..bfeb39c4 100644 --- a/src/SPC/doctor/CheckListHandler.php +++ b/src/SPC/doctor/CheckListHandler.php @@ -7,105 +7,62 @@ namespace SPC\doctor; use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\store\FileSystem; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ConfirmationQuestion; -class CheckListHandler +final class CheckListHandler { /** @var AsCheckItem[] */ private array $check_list = []; private array $fix_map = []; + public function __construct() {} + /** + * @return array * @throws \ReflectionException + * @throws RuntimeException * @throws FileSystemException - * @throws RuntimeException */ - public function __construct(private readonly InputInterface $input, private readonly OutputInterface $output, bool $include_manual = false) + public function runChecks(bool $include_manual = false): array { - $this->loadCheckList($include_manual); + return $this->loadCheckList($include_manual); } /** * @throws RuntimeException */ - public function runSingleCheck(string $item_name, int $fix_policy = FIX_POLICY_DIE): void + public function emitFix(OutputInterface $output, CheckResult $result): void { - foreach ($this->check_list as $item) { - if ($item->item_name === $item_name) { - $this->check_list = [$item]; - break; - } - } - $this->runCheck($fix_policy); - } + pcntl_signal(SIGINT, function () use ($output) { + $output->writeln('You cancelled fix'); + }); - /** - * @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; - } - } + $fix_result = call_user_func($this->fix_map[$result->getFixItem()], ...$result->getFixParams()); + pcntl_signal(SIGINT, SIG_IGN); + + if ($fix_result) { + $output->writeln('Fix done'); + } else { + $output->writeln('Fix failed'); + throw new RuntimeException('Some check item are not fixed'); } } /** * Load Doctor check item list * + * @return array * @throws \ReflectionException * @throws RuntimeException * @throws FileSystemException */ - private function loadCheckList(bool $include_manual = false): void + private function loadCheckList(bool $include_manual = false): array { 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) { + foreach ($method->getAttributes() as $a) { if (is_a($a->getName(), AsCheckItem::class, true)) { /** @var AsCheckItem $instance */ $instance = $a->newInstance(); @@ -126,26 +83,10 @@ class CheckListHandler } } } - // 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'); - } + // sort check list by level + usort($this->check_list, fn (AsCheckItem $a, AsCheckItem $b) => $a->level > $b->level ? -1 : ($a->level == $b->level ? 0 : 1)); + + return $this->check_list; } } diff --git a/tests/SPC/doctor/CheckListHandlerTest.php b/tests/SPC/doctor/CheckListHandlerTest.php new file mode 100644 index 00000000..28b37cf5 --- /dev/null +++ b/tests/SPC/doctor/CheckListHandlerTest.php @@ -0,0 +1,21 @@ +assertCount(6, $list->runChecks()); + } +}