| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- <?php
- declare(strict_types=1);
- namespace Glacier\Core;
- /**
- * 模块加载器(子空间机制,架构约定 docs/architecture.md §4)。
- *
- * 扫描 app/modules/<name>/,每个目录即一个独立子空间(/<name> 前缀 URL):
- * - module.php 模块声明(名称/描述/访问要求预留)
- * - routes.php 模块内路由(相对模式,如 '/'、'/item/:id')
- * - controllers/ 模块控制器(手动 require 加载,规避 PSR-4 目录大小写陷阱)
- * - views/ 模块模板(注册进 Twig)
- *
- * 安全:模块名白名单 [a-z0-9_-](小写),杜绝路径穿越;URL 与物理路径解耦。
- */
- final class ModuleLoader
- {
- /** @var list<array{name:string, declaration:array}> */
- private array $modules = [];
- /**
- * 注册所有模块路由到主 Router(在根路由表之后调用)。
- * @return list<array{name:string, declaration:array}> 已加载模块清单
- */
- public function load(Router $router): array
- {
- $modulesDir = APP_ROOT . '/app/modules';
- foreach (glob($modulesDir . '/*', GLOB_ONLYDIR) ?: [] as $dir) {
- $name = basename($dir);
- // 模块名白名单:小写字母/数字/下划线/连字符(与 URL 段一致)
- if (!preg_match('/^[a-z][a-z0-9_-]*$/', $name)) {
- Logger::warning('module skipped (invalid name)', ['name' => $name]);
- continue;
- }
- $declaration = $this->declaration($dir);
- $this->registerModule($router, $name, $dir);
- $this->modules[] = ['name' => $name, 'declaration' => $declaration];
- Logger::info('module loaded', ['name' => $name]);
- }
- return $this->modules;
- }
- private function registerModule(Router $router, string $name, string $dir): void
- {
- // 手动加载模块控制器(类文件按约定命名,见模块内 README 约定)
- $controllersDir = $dir . '/controllers';
- if (is_dir($controllersDir)) {
- foreach (glob($controllersDir . '/*.php') ?: [] as $file) {
- require_once $file;
- }
- }
- // 模块模板目录注册进 Twig(命名空间 = 模块名,模板用 @<name>/xxx 引用)
- $viewsDir = $dir . '/views';
- if (is_dir($viewsDir)) {
- View::addPath($viewsDir, $name);
- }
- // 模块内路由(相对模式)拼上 /<name> 前缀
- $routesFile = $dir . '/routes.php';
- if (!is_file($routesFile)) {
- return;
- }
- foreach ((array) require $routesFile as $route) {
- $router->add(
- $route['method'],
- '/' . $name . ($route['pattern'] === '/' ? '' : $route['pattern']),
- $route['handler'],
- $route['middleware'] ?? []
- );
- }
- }
- /** @return array 模块声明(缺失时给默认)。 */
- private function declaration(string $dir): array
- {
- $file = $dir . '/module.php';
- if (!is_file($file)) {
- return [];
- }
- $declaration = require $file;
- return is_array($declaration) ? $declaration : [];
- }
- }
|