docs: 开发规范/教程/demo 全套 + 修复插件运行期自动加载缺口
- demo 插件 plugins/demo.hello-world:演示短代码过滤器、comment.created 钩子、路由+命名空间视图、迁移+模型、后台设置页、filament.post_form 表单注入 - demo 主题 themes/demo:最小可运行主题(覆盖 partials + 绿色系样式) - 教程 docs/tutorial-plugin.md / docs/tutorial-theme.md(10 分钟上手);规范 docs/development.md(代码/插件/主题/队列/测试/提交) - README 增加开发者文档导航与上手示例 - 修复关键缺口:插件类原来靠 composer.json 硬编码加载,第三方 ZIP 安装的插件无法加载;现改为启动时扫描 plugins/*/src 运行期注册 PSR-4(register 阶段,保证 Filament 面板解析插件页面前就绪),composer.json 移除硬编码 - 测试:demo 插件 3 个用例 + demo 主题渲染;全量 89 通过
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# 插件开发教程(10 分钟上手)
|
||||
|
||||
配套示例:**`plugins/demo.hello-world/`**。本文带你从零写一个同样功能的插件,每个文件都有对照。
|
||||
|
||||
## 1. 目录结构
|
||||
|
||||
```
|
||||
plugins/{vendor}.{name}/ # 目录名必须 vendor.name 格式(如 demo.hello-world)
|
||||
├── plugin.json # 插件清单(必填)
|
||||
├── src/
|
||||
│ ├── ServiceProvider.php # 入口类(必填,继承基类)
|
||||
│ ├── Http/ # 前台控制器
|
||||
│ ├── Models/ # 模型
|
||||
│ └── Filament/Pages/ # 后台页面
|
||||
├── routes/web.php # 可选:前台路由(boot 时自动加载)
|
||||
├── database/migrations/ # 可选:迁移(migrate 自动执行)
|
||||
└── views/ # 可选:视图(注册命名空间后引用)
|
||||
```
|
||||
|
||||
## 2. plugin.json
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Hello World 示例插件",
|
||||
"version": "1.0.0",
|
||||
"description": "一句话描述",
|
||||
"usage": "使用说明(多行文本,后台插件卡片可折叠查看)",
|
||||
"author": "你的名字",
|
||||
"type": "demo",
|
||||
"provider": "Plugins\\Demo\\HelloWorld\\ServiceProvider",
|
||||
"requires": ["neatstudio.payment"], // 可选:依赖的插件
|
||||
"filament_pages": ["Plugins\\Demo\\HelloWorld\\Filament\\Pages\\HelloWorldSettings"],
|
||||
"filament_resources": ["...\\Resources\\XxxResource"]
|
||||
}
|
||||
```
|
||||
|
||||
字段说明见 `docs/plugins.md`。`provider` 指向入口类;缺省时系统会自动推断 `src/ServiceProvider.php`。
|
||||
|
||||
## 3. 入口类 ServiceProvider
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugins\Demo\HelloWorld;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
|
||||
class ServiceProvider extends PluginServiceProvider
|
||||
{
|
||||
protected function boot(PluginManager $manager): void
|
||||
{
|
||||
$this->loadRoutes(__DIR__.'/../routes/web.php'); // 加载路由
|
||||
$this->loadViews(__DIR__.'/../views', 'demo-hello'); // 注册视图命名空间 demo-hello::
|
||||
|
||||
// 过滤器:文章渲染后处理(返回值传给下一个过滤器)
|
||||
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
|
||||
return $html;
|
||||
}, 20);
|
||||
|
||||
// 动作:监听事件(无返回值)
|
||||
$manager->addAction('comment.created', function (\App\Models\Comment $comment) {
|
||||
// 做点什么
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 入口类**不要**命名为 `PluginServiceProvider`(与基类短名冲突),统一用 `ServiceProvider`。
|
||||
|
||||
> **类自动加载**:系统启动时自动扫描 `plugins/*/src` 并按 `Plugins\{Vendor}\{Name}` 注册 PSR-4——**无需修改 composer.json**,第三方 ZIP 安装的插件同样生效。
|
||||
|
||||
### 常用钩子
|
||||
|
||||
| Hook | 类型 | 参数 | 用途 |
|
||||
|------|------|------|------|
|
||||
| `post.rendered` | filter | `(string $html, Post) : string` | 文章渲染后处理(短代码/付费过滤) |
|
||||
| `comment.created` | action | `Comment` | 新评论创建 |
|
||||
| `payment.paid` | action | `Payment` | 支付成功(按 `$payment->payable` 分发) |
|
||||
| `filament.post_form` | action | `Schema` | 文章编辑表单追加字段 |
|
||||
| `filament.post_table` | action | `Table` | 文章列表追加列 |
|
||||
| `seo.structured_data` | filter | `(array $data) : array` | 扩展 JSON-LD |
|
||||
|
||||
完整列表见 `docs/plugins.md`。
|
||||
|
||||
## 4. 短代码示例(过滤器)
|
||||
|
||||
```php
|
||||
$manager->addFilter('post.rendered', function (string $html, Post $post) {
|
||||
$greeting = (string) \App\Models\Setting::get('hello_world_greeting', '你好');
|
||||
|
||||
return preg_replace_callback(
|
||||
'/\[hello\s+([^\]]+)\]/',
|
||||
fn (array $m) => '<span class="hello-world">'.$greeting.','.e($m[1]).'!</span>',
|
||||
$html
|
||||
);
|
||||
}, 20);
|
||||
```
|
||||
|
||||
文章里写 `[hello 世界]`,渲染时变成「你好,世界!」。`e()` 转义用户输入防 XSS。
|
||||
|
||||
## 5. 路由 + 视图
|
||||
|
||||
`routes/web.php`:
|
||||
|
||||
```php
|
||||
Route::get('/hello-world', [HelloWorldController::class, 'index'])->name('hello-world.index');
|
||||
```
|
||||
|
||||
控制器返回插件视图(命名空间 `demo-hello::`):
|
||||
|
||||
```php
|
||||
return view('demo-hello::hello', compact('logs'));
|
||||
```
|
||||
|
||||
视图里可以 `@include('partials.header')` 复用当前主题的公共片段。
|
||||
|
||||
## 6. 迁移 + 模型
|
||||
|
||||
迁移放在 `database/migrations/`,**无需手动 --path**,`php artisan migrate` 自动执行(AppServiceProvider 已注册 `plugins/*/database/migrations`)。
|
||||
|
||||
模型照常写,命名空间 `Plugins\Demo\HelloWorld\Models`。
|
||||
|
||||
## 7. 后台页面
|
||||
|
||||
`src/Filament/Pages/HelloWorldSettings.php`(继承 `Filament\Pages\Page` + `InteractsWithForms`),在 `plugin.json` 的 `filament_pages` 注册后自动出现在「插件」导航分组。可复用默认表单页视图 `filament.pages.plugin-settings`(见示例)。
|
||||
|
||||
## 8. 打包与安装
|
||||
|
||||
```bash
|
||||
cd plugins/demo.hello-world && zip -r ../demo.hello-world.zip . -x ".*"
|
||||
```
|
||||
|
||||
后台「插件管理」→ 上传 ZIP 安装 → 启用。目录名即包键,重复安装会提示已存在。
|
||||
|
||||
## 9. 测试你的插件
|
||||
|
||||
```bash
|
||||
# 快速验证短代码
|
||||
php artisan tinker --execute="
|
||||
\Plugins\Demo\HelloWorld\ServiceProvider::bootPlugin(app(\App\Blog\Support\PluginManager::class));
|
||||
\$html = '正文 [hello 世界]';
|
||||
echo app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', \$html, \App\Models\Post::first());
|
||||
"
|
||||
```
|
||||
|
||||
建议为插件写 Feature 测试(参照 `tests/Feature/` 现有用例)。
|
||||
|
||||
## 常见问题
|
||||
|
||||
- **改了代码不生效**:后台启停一次,或重启队列消费者(workerman/dev:watch)
|
||||
- **依赖其他插件**:在 `requires` 声明,系统会强制校验(依赖未启用无法启用)
|
||||
- **想让商品出现在订单页**:实体实现 `payableLabel()` / `payableUrl()` 两个方法(见 `docs/plugins.md`「订单商品化」)
|
||||
- **不想污染核心**:后台表单/表格一律走 `filament.post_form` / `filament.post_table` 钩子注入,核心零改动
|
||||
Reference in New Issue
Block a user