13 KiB
name, version, description, requires
| name | version | description | requires | |
|---|---|---|---|---|
| module-scaffold | 2.1.0 | 生成 Hyperf 模块化业务模块脚手架(ConfigProvider/Controller/Service/Model)。当需要新建业务模块、创建 module、添加模块化功能时使用。基于 ModuleLoader 自动发现 + ConfigProvider 机制,无需修改 composer.json。 |
|
⚠️ 核心执行流程已在
.cursor/rules/skill-module-scaffold.mdc中由 Cursor 自动注入。 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
Hyperf Module Scaffold
触发条件
用户要求创建新的业务模块、在 modules/ 下新建功能模块、或提到"模块化"、"新模块"、"module"。
前置条件
项目已完成模块化架构初始化:
Case-Database-Backend/modules/目录存在app/Support/ModuleLoader.php已存在并注册到composer.jsonautoload.filesconfig/autoload/annotations.php已包含BASE_PATH . '/modules'扫描路径
若未初始化,先引导用户完成架构搭建。
工作原理
ModuleLoader.php 在 vendor/autoload.php 加载时(Hyperf DI 容器初始化之前)自动执行:
- 扫描
modules/*/composer.json - 读取每个模块的
extra.hyperf.config - 通过反射注入
Hyperf\Support\Composer::$extra - 同时注册模块的 PSR-4 命名空间到 Composer ClassLoader
这使得 ProviderConfig::load() 能发现所有模块的 ConfigProvider,无需将模块添加到主项目 composer.json 的 require 中。
执行流程
1. 确认模块规格
| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| 模块名称 | ✅ | — | PascalCase,如 Order、UserCenter |
| 模块描述 | ❌ | 推断 | 一句话说明模块职责 |
| 接口类型 | ❌ | api |
api(用户端)/ admin(管理端)/ both(两者都有) |
| 需要 Service? | ❌ | true | 业务逻辑层 |
| 需要 Model? | ❌ | false | 数据模型 |
| 需要 Repository? | ❌ | false | 复杂查询时 |
| 需要 Middleware? | ❌ | false | 模块级中间件 |
| 需要 Request? | ❌ | false | 表单验证 |
| 路由前缀 | ❌ | 见下方规则 | 自动推断,也可手动指定 |
2. Controller 与 Request 目录结构规则(核心)
根据接口类型,Controller 和 Request 均放入对应子目录,命名空间随之变更:
| 接口类型 | 路由前缀 | Controller 目录 | Request 目录 | PHP 命名空间(Controller) |
|---|---|---|---|---|
用户端 API(/api/*) |
/api/{module-kebab} |
Http/Controller/Api/ |
Http/Request/Api/ |
Modules\{Name}\Http\Controller\Api |
管理端 API(/admin/*) |
/admin/{module-kebab} |
Http/Controller/Admin/ |
Http/Request/Admin/ |
Modules\{Name}\Http\Controller\Admin |
两者都有(both) |
各自前缀 | 两个 Controller 子目录 | 两个 Request 子目录 | 各自命名空间 |
判断规则:
- 路由前缀包含
/api/→ Controller 放Controller/Api/,Request 放Request/Api/ - 路由前缀包含
/admin/→ Controller 放Controller/Admin/,Request 放Request/Admin/ - 用户未指定接口类型时,优先询问,或根据模块职责推断
- 若模块同时有用户端和管理端接口,两组子目录都创建
- 类名不需要加接口类型前缀,子目录本身已区分,类名保持简洁
3. 生成目录结构
仅有用户端 API(最常见):
modules/{ModuleName}/
├── composer.json
└── src/
├── ConfigProvider.php
├── Http/
│ ├── Controller/
│ │ └── Api/ # 用户端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ └── Api/ # 用户端请求验证
│ │ └── {Name}Request.php
│ └── Middleware/ # 模块中间件(按需)
├── Service/
├── Model/ # 按需
├── Repository/ # 按需
├── Event/ # 按需
├── Listener/ # 按需
└── Constants/ # 按需
仅有管理端 API:
modules/{ModuleName}/
└── src/
├── Http/
│ ├── Controller/
│ │ └── Admin/ # 管理端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ └── Admin/ # 管理端请求验证
│ │ └── {Name}Request.php
...
同时有用户端 + 管理端(both):
modules/{ModuleName}/
└── src/
├── Http/
│ ├── Controller/
│ │ ├── Api/ # 用户端控制器
│ │ │ └── {Name}Controller.php
│ │ └── Admin/ # 管理端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ ├── Api/ # 用户端请求验证
│ │ │ └── {Name}Request.php
│ │ └── Admin/ # 管理端请求验证
│ │ └── {Name}Request.php
...
4. 生成 composer.json
{
"name": "modules/{module-kebab}",
"description": "{模块描述}",
"type": "library",
"autoload": {
"psr-4": {
"Modules\\{ModuleName}\\": "src/"
}
},
"extra": {
"hyperf": {
"config": "Modules\\{ModuleName}\\ConfigProvider"
}
}
}
命名规则:
- Composer 包名:
modules/{kebab-case},如modules/user-center - PHP 命名空间:
Modules\{PascalCase},如Modules\UserCenter
如模块有独立的第三方依赖,可在模块 composer.json 的 require 中声明,wikimedia/composer-merge-plugin 会在下次 composer update 时合并到主项目。
5. 生成 ConfigProvider.php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName};
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
// Interface::class => Implementation::class,
],
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
'publish' => [],
];
}
}
ConfigProvider 职责:
dependencies:注册模块的 DI 绑定(接口 → 实现)annotations.scan.paths:注册注解扫描路径(__DIR__指向src/)publish:可发布的配置文件(可选)
6. 生成 Controller 模板
用户端 API Controller(放在 Http/Controller/Api/):
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Controller\Api;
use App\Controller\AbstractController;
use App\Support\ResponseTrait;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Modules\{ModuleName}\Service\{ModuleName}Service;
use Psr\Http\Message\ResponseInterface;
#[Controller(prefix: '/api/{module-kebab}')]
class {ModuleName}Controller extends AbstractController
{
use ResponseTrait;
#[Inject]
protected {ModuleName}Service ${moduleName}Service;
#[GetMapping(path: '')]
public function index(): ResponseInterface
{
return $this->success([]);
}
}
管理端 API Controller(放在 Http/Controller/Admin/):
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Controller\Admin;
use App\Controller\AbstractController;
use App\Middleware\JwtAuthMiddleware;
use App\Support\ResponseTrait;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\Middleware;
use Modules\{ModuleName}\Service\{ModuleName}Service;
use Psr\Http\Message\ResponseInterface;
#[Controller(prefix: '/admin/{module-kebab}')]
#[Middleware(JwtAuthMiddleware::class)]
class {ModuleName}Controller extends AbstractController
{
use ResponseTrait;
#[Inject]
protected {ModuleName}Service ${moduleName}Service;
#[GetMapping(path: '')]
public function index(): ResponseInterface
{
return $this->success([]);
}
}
关键区别:
Api/下:前缀/api/,命名空间含\Api\Admin/下:前缀/admin/,命名空间含\Admin\,自动加JwtAuthMiddleware- 两者类名相同(均为
{Name}Controller),由命名空间区分,无需加接口类型前缀
7. 生成 Request 模板
用户端 Request(放在 Http/Request/Api/):
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Request\Api;
use Hyperf\Validation\Request\FormRequest;
class {Name}Request extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
// 'field' => 'required|string|max:255',
];
}
public function messages(): array
{
return [];
}
}
管理端 Request(放在 Http/Request/Admin/):
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Request\Admin;
use Hyperf\Validation\Request\FormRequest;
class {Name}Request extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
// 'field' => 'required|string|max:255',
];
}
public function messages(): array
{
return [];
}
}
关键区别:
Api/下:命名空间含\Request\Api\,类名{Name}RequestAdmin/下:命名空间含\Request\Admin\,类名{Name}Request- 两者类名相同,由命名空间区分,无需加接口类型前缀
- Controller 中
use路径需对应各自子命名空间
8. 验证模块自动发现
创建文件后无需任何 composer 操作,直接验证:
docker exec hyperf-skeleton php -r "
define('BASE_PATH', '/opt/www');
require '/opt/www/vendor/autoload.php';
\$extra = Hyperf\Support\Composer::getMergedExtra('hyperf');
echo in_array('Modules\\{ModuleName}\\ConfigProvider', \$extra['config'] ?? []) ? 'OK' : 'FAIL';
"
模块间依赖
若模块 A 依赖模块 B 的类:
- 通过 DI 注入模块 B 的 Service(不直接依赖 Controller/Model)
- 遵循依赖方向:上层模块 → 下层模块,禁止循环依赖
- ModuleLoader 的 glob 扫描天然支持多模块并存,无需额外配置
已有模块示例参考
当前项目 modules/Auth/ 的实际结构(参考标准):
modules/Auth/src/Http/Controller/
├── Api/ # 用户端接口(推荐新规范)
│ ├── AuthController.php # prefix: /api/auth
│ ├── CaptchaController.php # prefix: /api/auth/captcha
│ └── SmsController.php # prefix: /api/auth/sms
└── Admin/ # 管理端接口(推荐新规范)
└── AdminAuthController.php # prefix: /admin/auth
注:当前 Auth 模块尚未按此规范重构(所有 Controller 平铺在
Controller/下),新模块应从一开始遵循子目录规范。
验证
- 模块目录结构完整(composer.json + src/ConfigProvider.php + Http/Controller/)
- Controller 按接口类型放入正确子目录:
/api/*→Controller/Api/,/admin/*→Controller/Admin/ - Controller 命名空间包含正确子层级:
...\Http\Controller\Api\或...\Http\Controller\Admin\ Admin/下的 Controller 自动添加JwtAuthMiddleware;类名与Api/下保持一致,无需加前缀- Request 按接口类型放入正确子目录:
/api/*→Request/Api/,/admin/*→Request/Admin/ - Request 命名空间包含正确子层级:
...\Http\Request\Api\或...\Http\Request\Admin\ - Controller 中
use的 Request 路径与子目录一致 - composer.json 的
name使用modules/{kebab-case}格式 - namespace 使用
Modules\{PascalCase}格式 - ConfigProvider 注册了
__DIR__注解扫描路径 extra.hyperf.config指向正确的 ConfigProvider 类- 无需修改主项目
composer.json,模块被 ModuleLoader 自动发现 ProviderConfig::load()能发现模块 ConfigProviderphp -l所有 PHP 文件语法正确- Controller 路由前缀遵循 RESTful 命名(kebab-case)