Introduce AttributeMapper for managing extensions and doctor attributes

This commit is contained in:
crazywhalecc 2025-08-06 20:35:52 +08:00 committed by Jerry Ma
parent e28580de00
commit 722bb31815
14 changed files with 338 additions and 265 deletions

View File

@ -46,20 +46,14 @@ abstract class BuilderBase
/**
* Convert libraries to class
*
* @param array<string> $sorted_libraries Libraries to build (if not empty, must sort first)
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
* @param array<string> $sorted_libraries Libraries to build (if not empty, must sort first)
*
* @internal
*/
abstract public function proveLibs(array $sorted_libraries);
/**
* Set-Up libraries
*
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
public function setupLibs(): void
{
@ -139,9 +133,6 @@ abstract class BuilderBase
/**
* Check if there is a cpp extensions or libraries.
*
* @throws FileSystemException
* @throws WrongUsageException
*/
public function hasCpp(): bool
{
@ -174,15 +165,10 @@ abstract class BuilderBase
/**
* Verify the list of "ext" extensions for validity and declare an Extension object to check the dependencies of the extensions.
*
* @throws FileSystemException
* @throws RuntimeException
* @throws \ReflectionException
* @throws \Throwable|WrongUsageException
* @internal
*/
public function proveExts(array $static_extensions, array $shared_extensions = [], bool $skip_check_deps = false, bool $skip_extract = false): void
{
CustomExt::loadCustomExt();
// judge ext
foreach ($static_extensions as $ext) {
// if extension does not support static build, throw exception
@ -213,7 +199,7 @@ abstract class BuilderBase
}
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
$class = CustomExt::getExtClass($extension);
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
/** @var Extension $ext */
$ext = new $class($extension, $this);
if (in_array($extension, $static_extensions)) {
@ -247,11 +233,6 @@ abstract class BuilderBase
*/
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
/**
* @throws WrongUsageException
* @throws RuntimeException
* @throws FileSystemException
*/
public function buildSharedExts(): void
{
$lines = file(BUILD_BIN_PATH . '/php-config');
@ -284,9 +265,6 @@ abstract class BuilderBase
/**
* Generate extension enable arguments for configure.
* e.g. --enable-mbstring
*
* @throws FileSystemException
* @throws WrongUsageException
*/
public function makeStaticExtensionArgs(): string
{
@ -321,9 +299,6 @@ abstract class BuilderBase
/**
* Get PHP Version ID from php-src/main/php_version.h
*
* @throws RuntimeException
* @throws WrongUsageException
*/
public function getPHPVersionID(): int
{

View File

@ -39,9 +39,6 @@ class imap extends LinuxLibraryBase
return false;
}
/**
* @throws RuntimeException
*/
protected function build(): void
{
if ($this->builder->getLib('openssl')) {

View File

@ -69,9 +69,6 @@ abstract class BaseCommand extends Command
}
}
/**
* @throws WrongUsageException
*/
abstract public function handle(): int;
protected function execute(InputInterface $input, OutputInterface $output): int
@ -79,22 +76,17 @@ abstract class BaseCommand extends Command
$this->input = $input;
$this->output = $output;
global $ob_logger;
if ($input->getOption('debug') || $output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$input->getOption('no-ansi'));
define('DEBUG_MODE', true);
} else {
$ob_logger = new ConsoleLogger(decorated: !$input->getOption('no-ansi'));
}
// init log
$this->initLogFiles();
// windows fallback
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {
$helper = new QuestionHelper();
$case = $prompt->default ? ' [Y/n] ' : ' [y/N] ';
$question = new ConfirmationQuestion($prompt->label . $case, $prompt->default);
return $helper->ask($input, $output, $question);
});
// init logger
$this->initConsoleLogger();
// load attribute maps
AttributeMapper::init();
// init windows fallback
$this->initWindowsPromptFallback($input, $output);
// init GlobalEnv
if (!$this instanceof BuildCommand) {
@ -178,4 +170,58 @@ abstract class BaseCommand extends Command
return true;
}));
}
/**
* Initialize spc log files.
*/
private function initLogFiles(): void
{
$log_dir = SPC_LOGS_DIR;
if (!file_exists($log_dir)) {
mkdir($log_dir, 0755, true);
} elseif (!$this->getOption('preserve-log')) {
// Clean up old log files
$files = glob($log_dir . '/*.log');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
}
}
/**
* Initialize console logger.
*/
private function initConsoleLogger(): void
{
global $ob_logger;
if ($this->input->getOption('debug') || $this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$this->input->getOption('no-ansi'));
define('DEBUG_MODE', true);
} else {
$ob_logger = new ConsoleLogger(decorated: !$this->input->getOption('no-ansi'));
}
$log_file_fd = fopen(SPC_OUTPUT_LOG, 'a');
$ob_logger->addLogCallback(function ($level, $output) use ($log_file_fd) {
if ($log_file_fd) {
fwrite($log_file_fd, strip_ansi_colors($output) . "\n");
}
return true;
});
}
/**
* Initialize Windows prompt fallback for laravel-prompts.
*/
private function initWindowsPromptFallback(InputInterface $input, OutputInterface $output): void
{
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {
$helper = new QuestionHelper();
$case = $prompt->default ? ' [Y/n] ' : ' [y/N] ';
$question = new ConfirmationQuestion($prompt->label . $case, $prompt->default);
return $helper->ask($input, $output, $question);
});
}
}

View File

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\doctor\CheckListHandler;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
use SPC\doctor\DoctorHandler;
use SPC\util\AttributeMapper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use ZM\Logger\ConsoleColor;
use function Laravel\Prompts\confirm;
@ -16,72 +18,78 @@ class DoctorCommand extends BaseCommand
{
public function configure(): void
{
$this->addOption('auto-fix', null, null, 'Automatically fix failed items (if possible)');
$this->addOption('auto-fix', null, InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
}
public function handle(): int
{
try {
$checker = new CheckListHandler();
// skipped items
$skip_items = array_filter(explode(',', getenv('SPC_SKIP_DOCTOR_CHECK_ITEMS') ?: ''));
$fix_policy = match ($this->input->getOption('auto-fix')) {
'never' => FIX_POLICY_DIE,
true, null => FIX_POLICY_AUTOFIX,
default => FIX_POLICY_PROMPT,
};
$fix_map = AttributeMapper::getDoctorFixMap();
$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;
foreach (DoctorHandler::getValidCheckList() as $check) {
// output
$this->output->write("Checking <comment>{$check->item_name}</comment> ... ");
// null => skipped
if (($result = call_user_func($check->callback)) === null) {
$this->output->writeln('skipped');
continue;
}
// invalid return value => skipped
if (!$result instanceof CheckResult) {
$this->output->writeln('<error>Skipped due to invalid return value</error>');
continue;
}
// true => OK
if ($result->isOK()) {
/* @phpstan-ignore-next-line */
$this->output->writeln($result->getMessage() ?? (string) ConsoleColor::green('✓'));
continue;
}
// Failed => output error message
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
// If the result is not fixable, fail immediately
if ($result->getFixItem() === '') {
$this->output->writeln('This check item can not be fixed !');
return static::FAILURE;
}
if (!isset($fix_map[$result->getFixItem()])) {
$this->output->writeln("<error>Internal error: Unknown fix item: {$result->getFixItem()}</error>");
return static::FAILURE;
}
// prompt for fix
if ($fix_policy === FIX_POLICY_PROMPT) {
if (!confirm('Do you want to fix it?')) {
$this->output->writeln('<comment>You canceled fix.</comment>');
return static::FAILURE;
}
$this->output->write('Checking <comment>' . $check->item_name . '</comment> ... ');
// check if this item is skipped
if (in_array($check->item_name, $skip_items) || ($result = call_user_func($check->callback)) === null) {
$this->output->writeln('skipped');
} elseif ($result instanceof CheckResult) {
if ($result->isOK()) {
$this->output->writeln($result->getMessage() ?? 'ok');
continue;
}
// Failed
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
switch ($fix_policy) {
case FIX_POLICY_DIE:
throw new RuntimeException('Some check items can not be fixed !');
case FIX_POLICY_PROMPT:
if ($result->getFixItem() !== '') {
$question = confirm('Do you want to fix it?');
if ($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;
}
if (DoctorHandler::emitFix($this->output, $result)) {
$this->output->writeln('<info>Fix applied successfully!</info>');
} else {
$this->output->writeln('<error>Failed to apply fix!</error>');
return static::FAILURE;
}
}
$this->output->writeln('<info>Doctor check complete !</info>');
} catch (\Throwable $e) {
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
if (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, SIG_IGN);
// auto fix
if ($fix_policy === FIX_POLICY_AUTOFIX) {
$this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...');
if (DoctorHandler::emitFix($this->output, $result)) {
$this->output->writeln('<info>Fix applied successfully!</info>');
} else {
$this->output->writeln('<error>Failed to apply fix!</error>');
return static::FAILURE;
}
}
return static::FAILURE;
}
$this->output->writeln('<info>Doctor check complete !</info>');
return static::SUCCESS;
}
}

View File

@ -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) {}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace SPC\util;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\OptionalCheck;
use SPC\store\FileSystem;
/**
* AttributeMapper is responsible for mapping extension names to their respective classes
* using PHP attributes.
*
* This class scans the extension classes for the CustomExt attribute and builds a mapping
* of extension names to their class names, which can be used to retrieve the class by name.
* @internal it is intended for internal use within the SPC builder framework
*/
class AttributeMapper
{
/** @param array<string, string> $extensions The mapping of extension names to their classes */
private static array $ext_attr_map = [];
/** @var array<string, array<string, array|callable>> $doctor_map The mapping of doctor modules */
private static array $doctor_map = [
'check' => [],
'fix' => [],
];
public static function init(): void
{
// Load CustomExt attributes from extension classes
self::loadExtensionAttributes();
// Load doctor check items
self::loadDoctorAttributes();
// TODO: 3.0, refactor library loader and vendor loader here
}
/**
* Get the class name of an extension by its attributed name.
*
* @param string $name The name of the extension (attributed name)
* @return null|string Returns the class name of the extension if it exists, otherwise null
*/
public static function getExtensionClassByName(string $name): ?string
{
return self::$ext_attr_map[$name] ?? null;
}
/**
* @internal
*/
public static function getDoctorCheckMap(): array
{
return self::$doctor_map['check'];
}
/**
* @internal
*/
public static function getDoctorFixMap(): array
{
return self::$doctor_map['fix'];
}
private static function loadExtensionAttributes(): void
{
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/builder/extension', 'SPC\builder\extension');
foreach ($classes as $class) {
$reflection = new \ReflectionClass($class);
foreach ($reflection->getAttributes(CustomExt::class) as $attribute) {
/** @var CustomExt $instance */
$instance = $attribute->newInstance();
self::$ext_attr_map[$instance->ext_name] = $class;
}
}
}
private static function loadDoctorAttributes(): void
{
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/doctor/item', 'SPC\doctor\item');
foreach ($classes as $class) {
$optional_passthrough = null;
$ref = new \ReflectionClass($class);
// #[OptionalCheck]
$optional = $ref->getAttributes(OptionalCheck::class)[0] ?? null;
if ($optional !== null) {
/** @var OptionalCheck $instance */
$instance = $optional->newInstance();
if (is_callable($instance->check)) {
$optional_passthrough = $instance->check;
}
}
$check_items = [];
$fix_items = [];
// load check items and fix items
foreach ($ref->getMethods() as $method) {
$optional_passthrough_single = $optional_passthrough ?? null;
// #[OptionalCheck]
foreach ($method->getAttributes(OptionalCheck::class) as $method_attr) {
$optional_check = $method_attr->newInstance();
if (is_callable($optional_check->check)) {
$optional_passthrough_single = $optional_check->check;
}
}
// #[AsCheckItem]
foreach ($method->getAttributes(AsCheckItem::class) as $method_attr) {
// [{AsCheckItem object}, {OptionalCheck callable or null}]
$obj = $method_attr->newInstance();
$obj->callback = [new $class(), $method->getName()];
$check_items[] = [$obj, $optional_passthrough_single];
}
// #[AsFixItem]
$fix_item = $method->getAttributes(AsFixItem::class)[0] ?? null;
if ($fix_item !== null) {
// [{AsFixItem object}, {OptionalCheck callable or null}]
$obj = $fix_item->newInstance();
$fix_items[$obj->name] = [new $class(), $method->getName()];
}
}
// add to doctor map
self::$doctor_map['check'] = array_merge(self::$doctor_map['check'], $check_items);
self::$doctor_map['fix'] = array_merge(self::$doctor_map['fix'], $fix_items);
}
// sort check items by level
usort(self::$doctor_map['check'], fn (array $a, array $b) => $a[0]->level > $b[0]->level ? -1 : ($a[0]->level == $b[0]->level ? 0 : 1));
}
}

View File

@ -5,8 +5,6 @@ declare(strict_types=1);
namespace SPC\util;
use SPC\builder\Extension;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
/**
* Custom extension attribute and manager
@ -17,43 +15,10 @@ use SPC\store\FileSystem;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class CustomExt
{
private static array $custom_ext_class = [];
/**
* Constructor for custom extension attribute
*
* @param string $ext_name The extension name
*/
public function __construct(protected string $ext_name) {}
/**
* Load all custom extension classes
*
* This method scans the extension directory and registers all classes
* that have the CustomExt attribute.
*
* @throws \ReflectionException
* @throws FileSystemException
*/
public static function loadCustomExt(): void
{
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/builder/extension', 'SPC\builder\extension');
foreach ($classes as $class) {
$reflection = new \ReflectionClass($class);
foreach ($reflection->getAttributes(CustomExt::class) as $attribute) {
self::$custom_ext_class[$attribute->getArguments()[0]] = $class;
}
}
}
/**
* Get the class name for a custom extension
*
* @param string $ext_name The extension name
* @return string The class name for the extension
*/
public static function getExtClass(string $ext_name): string
{
return self::$custom_ext_class[$ext_name] ?? Extension::class;
}
public function __construct(public string $ext_name) {}
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace SPC\Tests\builder;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider;
@ -14,7 +13,7 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\util\CustomExt;
use SPC\util\AttributeMapper;
use SPC\util\DependencyUtil;
use Symfony\Component\Console\Input\ArgvInput;
@ -36,9 +35,8 @@ class BuilderTest extends TestCase
$this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
[$extensions, $libs] = DependencyUtil::getExtsAndLibs(['mbregex']);
$this->builder->proveLibs($libs);
CustomExt::loadCustomExt();
foreach ($extensions as $extension) {
$class = CustomExt::getExtClass($extension);
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
$ext = new $class($extension, $this->builder);
$this->builder->addExt($ext);
}

View File

@ -7,7 +7,7 @@ namespace SPC\Tests\builder;
use PHPUnit\Framework\TestCase;
use SPC\builder\BuilderProvider;
use SPC\builder\Extension;
use SPC\util\CustomExt;
use SPC\util\AttributeMapper;
use SPC\util\DependencyUtil;
use Symfony\Component\Console\Input\ArgvInput;
@ -23,9 +23,8 @@ class ExtensionTest extends TestCase
$builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
[$extensions, $libs] = DependencyUtil::getExtsAndLibs(['mbregex']);
$builder->proveLibs($libs);
CustomExt::loadCustomExt();
foreach ($extensions as $extension) {
$class = CustomExt::getExtClass($extension);
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
$ext = new $class($extension, $builder);
$builder->addExt($ext);
}

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace SPC\Tests\doctor;
use PHPUnit\Framework\TestCase;
use SPC\doctor\CheckListHandler;
use SPC\doctor\DoctorHandler;
/**
* @internal
@ -14,9 +14,9 @@ final class CheckListHandlerTest extends TestCase
{
public function testRunChecksReturnsListOfCheck(): void
{
$list = new CheckListHandler();
$list = new DoctorHandler();
$id = $list->runChecks();
$id = $list->getValidCheckList();
foreach ($id as $item) {
$this->assertInstanceOf('SPC\doctor\AsCheckItem', $item);
}

View File

@ -5,8 +5,6 @@ declare(strict_types=1);
namespace SPC\Tests\store;
use PHPUnit\Framework\TestCase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
/**

View File

@ -4,3 +4,5 @@ declare(strict_types=1);
require_once __DIR__ . '/../src/globals/internal-env.php';
require_once __DIR__ . '/mock/SPC_store.php';
\SPC\util\AttributeMapper::init();