Files
laralog/docs/tutorial-plugin.md
T
ak 8fdf8dbb4b 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 通过
2026-08-12 17:26:34 +08:00

157 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 插件开发教程(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` 钩子注入,核心零改动