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');
}
}
}