mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-10 10:25:36 +08:00
Introduce AttributeMapper for managing extensions and doctor attributes
This commit is contained in:
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\doctor;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
class AsFixItem
|
||||
{
|
||||
public function __construct(public string $name) {}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\doctor;
|
||||
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\store\FileSystem;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class CheckListHandler
|
||||
{
|
||||
/** @var AsCheckItem[] */
|
||||
private array $check_list = [];
|
||||
|
||||
private array $fix_map = [];
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* @return array<AsCheckItem>
|
||||
* @throws \ReflectionException
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function runChecks(bool $include_manual = false): array
|
||||
{
|
||||
return $this->loadCheckList($include_manual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function emitFix(OutputInterface $output, CheckResult $result): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
sapi_windows_set_ctrl_handler(function () use ($output) {
|
||||
$output->writeln('<error>You cancelled fix</error>');
|
||||
});
|
||||
} elseif (extension_loaded('pcntl')) {
|
||||
pcntl_signal(SIGINT, function () use ($output) {
|
||||
$output->writeln('<error>You cancelled fix</error>');
|
||||
});
|
||||
}
|
||||
|
||||
$fix_result = call_user_func($this->fix_map[$result->getFixItem()], ...$result->getFixParams());
|
||||
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
sapi_windows_set_ctrl_handler(null);
|
||||
} elseif (extension_loaded('pcntl')) {
|
||||
pcntl_signal(SIGINT, SIG_IGN);
|
||||
}
|
||||
|
||||
if ($fix_result) {
|
||||
$output->writeln('<info>Fix done</info>');
|
||||
} else {
|
||||
$output->writeln('<error>Fix failed</error>');
|
||||
throw new RuntimeException('Some check item are not fixed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Doctor check item list
|
||||
*
|
||||
* @return array<AsCheckItem>
|
||||
* @throws \ReflectionException
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
private function loadCheckList(bool $include_manual = false): array
|
||||
{
|
||||
foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\doctor\item') as $class) {
|
||||
$ref = new \ReflectionClass($class);
|
||||
$optional = $ref->getAttributes(OptionalCheck::class)[0] ?? null;
|
||||
if ($optional !== null) {
|
||||
/** @var OptionalCheck $instance */
|
||||
$instance = $optional->newInstance();
|
||||
if (is_callable($instance->check) && !call_user_func($instance->check)) {
|
||||
continue; // skip this class if optional check is false
|
||||
}
|
||||
}
|
||||
foreach ($ref->getMethods() as $method) {
|
||||
foreach ($method->getAttributes() 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 (AsCheckItem $a, AsCheckItem $b) => $a->level > $b->level ? -1 : ($a->level == $b->level ? 0 : 1));
|
||||
|
||||
return $this->check_list;
|
||||
}
|
||||
}
|
||||
63
src/SPC/doctor/DoctorHandler.php
Normal file
63
src/SPC/doctor/DoctorHandler.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\doctor;
|
||||
|
||||
use SPC\exception\SPCException;
|
||||
use SPC\util\AttributeMapper;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class DoctorHandler
|
||||
{
|
||||
/**
|
||||
* Returns a list of valid check items.
|
||||
*
|
||||
* @return array<AsCheckItem>
|
||||
*/
|
||||
public static function getValidCheckList(): iterable
|
||||
{
|
||||
foreach (AttributeMapper::getDoctorCheckMap() as [$item, $optional]) {
|
||||
/* @var AsCheckItem $item */
|
||||
// optional check
|
||||
if ($optional !== null && !call_user_func($optional)) {
|
||||
continue; // skip this when the optional check is false
|
||||
}
|
||||
// limit_os check
|
||||
if ($item->limit_os !== null && $item->limit_os !== PHP_OS_FAMILY) {
|
||||
continue;
|
||||
}
|
||||
// skipped items by env
|
||||
$skip_items = array_filter(explode(',', getenv('SPC_SKIP_DOCTOR_CHECK_ITEMS') ?: ''));
|
||||
if (in_array($item->item_name, $skip_items)) {
|
||||
continue; // skip this item
|
||||
}
|
||||
yield $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the fix for a given CheckResult.
|
||||
*
|
||||
* @param OutputInterface $output the output interface to write messages to
|
||||
* @param CheckResult $result the result of the check that needs fixing
|
||||
* @return bool returns true if the fix was successful, false otherwise
|
||||
*/
|
||||
public static function emitFix(OutputInterface $output, CheckResult $result): bool
|
||||
{
|
||||
keyboard_interrupt_register(function () use ($output) {
|
||||
$output->writeln('<error>You cancelled fix</error>');
|
||||
});
|
||||
try {
|
||||
$fix_result = call_user_func(AttributeMapper::getDoctorFixMap()[$result->getFixItem()], ...$result->getFixParams());
|
||||
} catch (SPCException $e) {
|
||||
$output->writeln('<error>Fix failed: ' . $e->getMessage() . '</error>');
|
||||
return false;
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>Fix failed with an unexpected error: ' . $e->getMessage() . '</error>');
|
||||
return false;
|
||||
}
|
||||
keyboard_interrupt_unregister();
|
||||
return $fix_result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user