setName('registry:check') ->setDescription('扩展点注册中心一致性校验(产品类型/交易模式/订单适配器/售后规则)'); } /** * 执行校验 * @param Input $input 输入对象 * @param Output $output 输出对象 * @return int 0=通过(允许存在 warning),1=存在 error 级问题 */ protected function execute(Input $input, Output $output) { $errors = []; $warnings = []; /** @var ProductTypeRegistry $productRegistry */ $productRegistry = $this->app->make(ProductTypeRegistry::class); /** @var TradeModeRegistry $tradeRegistry */ $tradeRegistry = $this->app->make(TradeModeRegistry::class); $productTypes = $productRegistry->all(); $tradeModes = $tradeRegistry->all(); $sourceTypes = OrderTypeRegistry::getRegisteredSourceTypes(); $output->writeln('扩展点注册中心一致性校验'); $output->writeln('产品类型:' . count($productTypes) . ' / 交易模式:' . count($tradeModes) . ' / 订单适配器:' . count($sourceTypes)); $output->writeln(str_repeat('-', 72)); // ── 1. 注册类合法性:类存在 + 实现契约接口(反射读注册表,不实例化、零副作用)── $this->checkClassLegality('ProductTypeRegistry', $this->readProtectedMap(ProductTypeRegistry::class, 'handlers'), ProductTypeHandlerInterface::class, $errors); $this->checkClassLegality('TradeModeRegistry', $this->readProtectedMap(TradeModeRegistry::class, 'handlers'), TradeModeHandlerInterface::class, $errors); $this->checkClassLegality('OrderTypeRegistry', $this->readProtectedMap(OrderTypeRegistry::class, 'adapters'), OrderTypeAdapterInterface::class, $errors); // AfterSaleRuleRegistry::register 期已做 is_subclass_of 校验,无需重复 // ── 2. 兜底完整性 ── if (!in_array('normal', $productTypes, true)) { $errors[] = 'ProductTypeRegistry 缺少兜底产品类型 normal(Registry::get 未注册类型将无兜底,运行期抛 RuntimeException)'; } if (!in_array('normal', $tradeModes, true)) { $errors[] = 'TradeModeRegistry 缺少普通购买交易模式 normal'; } if (!in_array('normal_order', $sourceTypes, true)) { $errors[] = 'OrderTypeRegistry 缺少兜底订单适配器 normal_order'; } // ── 3~5. 产品类型三维对齐:订单适配器 / 售后规则 / 交易模式兼容 ── foreach ($productTypes as $type) { try { $handler = $productRegistry->get($type); // 订单适配器对齐:缺失时中台解析订单状态抛「未注册的源表类型」 $sourceType = $handler->getOrderSourceType(); if (!in_array($sourceType, $sourceTypes, true)) { $errors[] = "产品类型 [{$type}] 声明源表类型 [{$sourceType}] 未在 OrderTypeRegistry 注册" . "(请检查 modules/product/{$type}/App.php 是否漏调 OrderTypeRegistry::register)"; } // 售后规则覆盖:未注册 product_type 规则的订单默认拒绝售后,属静默行为变化 if (AfterSaleRuleRegistry::getProvider('product_type', $type) === null) { $errors[] = "产品类型 [{$type}] 未注册 product_type 维度售后规则" . "(AfterSaleRuleRegistry::registerProductType,缺失将默认拒绝售后)"; } // 交易模式兼容有效:声明了未注册的模式,运行期下单会走兜底异常 // getCompatibility() 返回关联数组,模式列表在 supported_trade_modes 键下 foreach (($handler->getCompatibility()['supported_trade_modes'] ?? []) as $mode) { if (!in_array($mode, $tradeModes, true)) { // 插值前做标量防护,避免非标量声明值触发 Array to string conversion $modeLabel = is_scalar($mode) ? var_export($mode, true) : gettype($mode); $warnings[] = "产品类型 [{$type}] 声明兼容交易模式 [{$modeLabel}],但 TradeModeRegistry 未注册该模式"; } } } catch (\Throwable $e) { $errors[] = "产品类型 [{$type}] Handler 无法实例化或声明方法异常:" . $e->getMessage(); } } // 输出结果 if ($errors) { $output->error('error 级问题 ' . count($errors) . ' 个:'); foreach ($errors as $msg) { $output->writeln(' ❌ ' . $msg); } } else { $output->writeln('✅ error 级问题:0'); } if ($warnings) { $output->warning('warning 级问题 ' . count($warnings) . ' 个:'); foreach ($warnings as $msg) { $output->writeln(' ⚠️ ' . $msg); } } else { $output->writeln('✅ warning 级问题:0'); } if ($errors) { $output->error('校验未通过:注册中心存在非法/缺失注册,请修正模块 App.php 注册逻辑'); return 1; } $output->writeln('✅ 校验通过' . ($warnings ? '(存在兼容声明警告,请评估)' : '')); return 0; } /** * 校验注册表内所有类名合法(类存在 + 实现契约接口) * @param string $registryName 注册中心名称(用于报错提示) * @param array $map 注册表 key => 类名 * @param string $contract 契约接口完整类名 * @param array $errors 引用收集 error 消息 * @return void */ protected function checkClassLegality(string $registryName, array $map, string $contract, array &$errors): void { foreach ($map as $key => $class) { if (!class_exists($class)) { $errors[] = "{$registryName} 注册项 [{$key}] 类不存在:{$class}"; } elseif (!is_subclass_of($class, $contract)) { $errors[] = "{$registryName} 注册项 [{$key}] 未实现契约接口:{$class} 应实现 {$contract}"; } } } /** * 反射读取注册中心的 protected 静态/实例映射表(只读,不触发实例化) * @param string $class 注册中心类名 * @param string $prop 属性名(handlers / adapters) * @return array key => 类名映射表 */ protected function readProtectedMap(string $class, string $prop): array { try { $ref = new \ReflectionProperty($class, $prop); $ref->setAccessible(true); $value = $ref->isStatic() ? $ref->getValue() : $ref->getValue($this->app->make($class)); return is_array($value) ? $value : []; } catch (\Throwable $e) { return []; } } }