Files
gouki 3cec4c5e18
CI / PHPUnit (PHP 8.3) (push) Failing after 4s
CI / PHPUnit (PHP 8.2) (push) Failing after 1m9s
CI / Deploy (manual gate) (push) Skipped
wip: article AI polish, category SEO fields, cover generator, membership plan seeder
2026-09-07 18:48:37 +00:00

116 lines
4.5 KiB
Markdown
Raw Permalink 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.
# 插件开发指南
## 目录格式
```
plugins/{vendor}/{name}/
plugin.json
README.md # 默认 / 英文
README.zh_CN.md # 可选,后台中文界面优先
database/migrations/ # 可选
resources/views/ # 可选
src/PluginServiceProvider.php
```
### plugin.json
```json
{
"name": "vendor/my-plugin",
"title": "显示名",
"version": "1.0.0",
"description": "说明",
"provider": "Plugins\\Vendor\\MyPlugin\\PluginServiceProvider",
"requires": ["larablog/payment"],
"optional": [],
"docs": "README.md"
}
```
- `requires`:硬依赖,未启用时 `PluginManager::enable` 会拒绝
- `docs`:相对插件根目录的说明文件(默认 `README.md`)。后台按当前语言优先读 `README.{locale}.md`(如 `README.zh_CN.md`),再试语言前缀(如 `README.zh.md`),最后回退到 `docs` 指定文件。Markdown 会渲染成 HTML,在侧栏弹层中展示。只允许指向插件目录内(`..`、绝对路径、软链越界会被拒绝)
`composer.json``autoload.psr-4` 注册命名空间,然后:
```bash
composer dump-autoload
php artisan plugins:sync
# 启用后如有迁移:
php artisan migrate
```
## 生命周期
1. `PluginManager::discover()` 扫描磁盘
2. `plugins:sync` / 后台同步写入 `plugins`
3. 启用后 `registerEnabledProviders()` 注册 ProviderFilament panel 构建前也会注册,以便 `Panel::configureUsing`
4. Provider `register()` / `boot()` 内挂 Hook、路由、Filament Resource、迁移
## Hook 类型
| 方法 | 用途 |
|---|---|
| `listen` + `dispatch` | 事件通知(如 `comment.created``order.paid` |
| `gather` | 字符串管道(主题槽 `theme.*` |
| `collect` | 数组合并(Filament 组件/列/动作) |
| `filter` | 值管道(表单 mutate 等;每个监听器可替换值) |
| `listeners` | 取回监听器自行折叠(`article.access` 用它保证只许变严) |
```php
use App\Domain\Plugin\Hook;
Hook::listen('comment.created', function (Comment $comment): void { /* ... */ });
Hook::listen('theme.sidebar', function (string $html): string {
return $html.'<p>侧栏注入</p>';
});
Hook::listen('filament.article.form', function (array $components): array {
return [/* Section / Field */];
});
Hook::listen('article.access', function (AccessDecision $decision, array $ctx): AccessDecision {
return $decision->tightenWith(/* 更严决策 */);
});
```
## 约定扩展点(首批)
| 点 | 类型 | 用途 |
|---|---|---|
| `theme.*` | gather | 前台主题槽 |
| `article.access` | fold | 收紧阅读权限;核心逐个 `tightenWith` 折叠,放宽无效、非 `AccessDecision` 返回值忽略 |
| `filament.article.form` | collect | 文章表单追加组件 |
| `filament.article.table.columns` | collect | 文章表追加列 |
| `filament.article.table.filters` | collect | 文章表追加筛选 |
| `filament.article.table.query` | filter | 文章表查询(如子查询列) |
| `filament.article.actions` | collect | 文章动作 |
| `filament.article.mutate_before_fill` | filter | 编辑回填 |
| `filament.article.mutate_before_save` | filter | 保存前改数据(勿在此 unset 限制字段) |
| `filament.article.validate_access_restrictions` | filter | 密码/付费/会员互斥校验(strip 之前) |
| `filament.article.after_save` | dispatch | 保存后写插件表 |
| `order.paid` / `order.refunded` | dispatch | 支付插件发出 |
## 三类注入
1. **UI**:前台主题槽 + 后台 Filament collect
2. **功能**access / lifecycle Hook
3. **数据**:插件自有 migration/model,用外键或 `product_type`+`product_id` 关联
## Filament 页面/资源
放在插件命名空间,于 `register()` 中:
```php
Panel::configureUsing(function (Panel $panel): void {
if ($panel->getId() !== 'admin') return;
$panel->resources([OrderResource::class])->pages([SettingsPage::class]);
});
```
Resource / Page 需用 `canAccess` / `shouldRegisterNavigation` 检查插件已启用。
## 后台
- 「插件」页:卡片启停、依赖展示、使用说明
- 可选:继续用核心 `PluginSkeletonPage` 做极简占位
## 内置插件
| 插件 | 状态 |
|---|---|
| `larablog/ai-comment-moderation` | 可运行 |
| `larablog/payment` | Stub 订单/权益可跑通 |
| `larablog/paid-content` | 文章付费(依赖 payment |
| `larablog/membership` | 会员套餐 / Stub 订阅 / 会员可见文章(依赖 payment) |
| `larablog/plugin-marketplace` | 骨架 |
| `larablog/theme-marketplace` | 骨架 |