关于 laravel-specialist
这是一项用于设计、构建和维护 Laravel 应用的技能,涵盖架构、实现、测试与优化,并整合 Eloquent、Blade、Livewire、队列、事件和 Pest 的 Laravel 实践指南。
主要功能
- 01Laravel 项目与架构审查
- 02Eloquent 模型和关系
- 03Blade 与 Livewire 模式
- 04队列、作业、事件与中间件
- 05覆盖多层的 Pest 测试
- 06迁移、数据填充与 Artisan
适用场景
- 01规划新的 Laravel 应用
- 02审查现有项目架构
- 03构建数据模型和异步作业
- 04测试路由、数据库行为和用户流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill laravel-specialist安装后在 Claude Code 中输入/laravel-specialist确认已加载。
详细介绍
Laravel 专家
概述
遵循框架约定和最佳实践,设计、构建并维护可用于生产环境的 Laravel 应用。此技能涵盖完整的 Laravel 生态系统:具有高级关联模式的 Eloquent ORM、Blade 模板和 Livewire 交互功能、队列与事件系统、中间件管道、服务提供者、各层级的 Pest 测试,以及用于迁移、填充数据和工厂的 Artisan 工具。
无论是从零开始开发还是在现有项目上进行开发,只要应用使用 Laravel 作为框架,就应运用此技能。
多阶段流程
阶段 1:发现上下文
- 确认 Laravel 版本(
composer.json->laravel/framework) - 扫描
config/,了解已启用的包和自定义配置 - 梳理现有模型、关联关系和迁移历史
- 查看
routes/中的 API、Web、控制台和频道定义 - 列出已安装的第一方包(Sanctum、Horizon、Telescope、Pulse、Pennant、Scout、Cashier)
- 检查前端技术栈是否使用 Livewire、Inertia,或仅使用 Blade
停止 — 未确认 Laravel 版本和已安装的包之前,不要开始架构审查。
文档验证协议
[HARD-GATE] 如果不确定任何 Laravel API,请先验证,不要猜测。优先使用 mcp__context7__resolve-library-id,然后使用 mcp__context7__query-docs。备用方法:从 https://github.com/laravel/docs 获取文档。对于 Livewire、Pest、Inertia,请分别通过 context7 解析。返回的文档优先于记忆中的知识。
阶段 2:架构审查
- 确认目录结构符合 Laravel 约定(见下文相关章节)
- 评估服务提供者注册和延迟加载
- 检查中间件栈的顺序和分组
- 评估队列连接配置和工作进程拓扑
- 检查缓存策略(配置、路由、视图、应用级缓存)
停止 — 记录架构差距之前,不要开始实施。
阶段 3:实施
- 先编写迁移 — 数据库架构是事实来源
- 构建 Eloquent 模型,并定义关联关系、查询范围、类型转换和访问器
- 在专用的 Action 或 Service 类中实现业务逻辑
- 创建与路由绑定的控制器(单动作控制器或资源控制器)
- 添加用于验证的 Form Request,以及用于授权的 Policy
- 为异步工作流接入事件、监听器和作业
停止 — 不要跳过 Form Request 和 Policy。内联验证和授权属于反模式。
阶段 4:测试
- 为隔离逻辑(Action、值对象、类型转换)编写单元测试
- 为 HTTP 端点和中间件行为编写功能测试
- 使用 Laravel Dusk 为关键用户流程编写浏览器测试
- 使用
assertDatabaseHas、assertSoftDeleted进行数据库断言 - 使用队列和事件伪造功能验证副作用
停止 — 所有层级的测试通过之前,不要进行优化。
阶段 5:优化
- 使用预加载消除 N+1 查询
- 缓存开销较大的计算结果,以及配置、路由和视图
- 为频繁查询的列创建索引;使用
EXPLAIN进行验证 - 在开发环境中使用 Telescope 或 Debugbar 进行性能分析
- 配置 Horizon,以监控生产环境中的队列
Eloquent 模式
关联关系
| 关联关系 | 方法 | 反向关联 | 使用场景 |
|---|---|---|---|
| 一对一 | hasOne | belongsTo | 用户 -> 个人资料 |
| 一对多 | hasMany | belongsTo | 文章 -> 评论 |
| 多对多 | belongsToMany | belongsToMany | 用户 <-> 角色(中间表) |
| 穿透式一对多 | hasManyThrough | — | 国家 -> 文章(通过用户关联) |
| 多态关联 | morphMany / morphTo | morphTo | 文章和视频上的评论 |
| 多态多对多 | morphToMany | morphedByMany | 文章和视频上的标签 |
查询范围
// Local scope — reusable query constraint
public function scopeActive(Builder $query): Builder
{
return $query->where('status', 'active');
}
// Usage: User::active()->where('role', 'admin')->get();
// Global scope — applied to all queries on the model
protected static function booted(): void
{
static::addGlobalScope('published', function (Builder $builder) {
$builder->whereNotNull('published_at');
});
}
访问器、修改器和类型转换
// Attribute accessor/mutator (Laravel 11+ syntax)
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
// Custom cast
protected function casts(): array
{
return [
'options' => AsCollection::class,
'address' => AddressCast::class,
'status' => OrderStatus::class, // Backed enum
'metadata' => 'array',
'is_active' => 'boolean',
'amount' => MoneyCast::class,
];
}
使用预加载优化查询
// BAD — N+1 problem: 1 query for posts + N queries for authors
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Triggers lazy load each iteration
}
// GOOD — Eager load: 2 queries total
$posts = Post::with('author')->get();
// Nested eager loading
$posts = Post::with(['author', 'comments.user'])->get();
// Constrained eager loading
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)->latest()->limit(5);
}])->get();
// Prevent lazy loading in development
Model::preventLazyLoading(! app()->isProduction());
Blade 模板和 Livewire 组件
Blade 约定
- 布局:使用
resources/views/layouts/app.blade.php中的@yield/@section,或基于组件的<x-app-layout> - 组件:
resources/views/components/— 匿名组件或基于类的组件 - 局部视图:使用
@include('partials.sidebar')引入可复用片段 - 使用
{{ }}输出转义内容,仅在明确信任 HTML 时使用{!! !!} - 优先使用 Blade 指令(
@auth、@can、@env),而非原生 PHP 条件语句
Livewire 模式
// Full-page Livewire component (Livewire 3+)
#[Layout('layouts.app')]
#[Title('Dashboard')]
class Dashboard extends Component
{
public string $search = '';
#[Computed]
public function users(): LengthAwarePaginator
{
return User::where('name', 'like', "%{$this->search}%")->paginate(15);
}
public function render(): View
{
return view('livewire.dashboard');
}
}
前端技术栈决策表
| 决策 | 选择 Livewire | 选择 Inertia |
|---|---|---|
| 现有 Blade 代码库 | 是 | 否 |
| 需要类似 SPA 的体验 | 部分支持(使用 wire:navigate) | 是 |
| 团队熟悉 Vue/React | 否 | 是 |
| 优先考虑服务端渲染 | 是 | 取决于适配器 |
| 实时响应式交互 | 是(轮询、流) | 需要配置 Echo |
| SEO 至关重要的页面 | 两者均可 | 两者均可(SSR 适配器) |
队列、任务和事件模式
任务设计
class ProcessInvoice implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60;
public int $timeout = 120;
public string $queue = 'invoices';
public function __construct(public readonly Invoice $invoice) {}
public function handle(PdfGenerator $generator): void
{
$generator->generate($this->invoice);
}
public function failed(Throwable $exception): void
{
// Notify admin, log to error tracker
}
}
事件 / 监听器模式
// Dispatch event
OrderPlaced::dispatch($order);
// Listener (queued)
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->user)->send(new OrderConfirmationMail($event->order));
}
}
同步与异步决策表
| 任务 | 加入队列 | 同步 |
|---|---|---|
| 发送邮件 / 通知 | 是 | 绝不在请求周期内执行 |
| 生成 PDF | 是 | 仅当耗时少于 2 秒且用户等待时 |
| 支付处理 | 视情况而定 — 优先采用 Webhook 驱动 | 网关响应时间少于 5 秒时 |
| 预热缓存 | 是 | 绝不 |
| 审计日志记录 | 是(高频)或同步(低频) | 需要保证送达时 |
| 搜索索引 | 是 | 绝不 |
中间件和服务提供程序
中间件堆栈顺序
中间件顺序很重要。bootstrap/app.php 中的默认堆栈(Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
HandleInertiaRequests::class, // After session, before response
]);
$middleware->api(prepend: [
EnsureFrontendRequestsAreStateful::class, // Sanctum SPA auth
]);
$middleware->alias([
'role' => EnsureUserHasRole::class,
'verified' => EnsureEmailIsVerified::class,
]);
})
服务提供程序最佳实践
- 在
register()中注册绑定,绝不要在其中从容器解析依赖 - 启动逻辑(事件监听器、路由模型绑定、宏)放在
boot()中 - 对并非每个请求都需要的绑定,使用延迟加载的提供程序
- 避免在提供程序中执行繁重逻辑 — 委托给专用类
使用 Pest 进行测试
单元测试
test('order total calculates tax correctly', function () {
$order = Order::factory()->make(['subtotal' => 10000, 'tax_rate' => 0.08]);
expect($order->total)->toBe(10800);
});
功能测试
test('authenticated user can create a post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/posts', [
'title' => 'My Post',
'body' => 'Content here.',
]);
$response->assertCreated()
->assertJsonPath('data.title', 'My Post');
$this->assertDatabaseHas('posts', [
'user_id' => $user->id,
'title' => 'My Post',
]);
});
队列和事件伪造
test('placing an order dispatches confirmation job', function () {
Queue::fake();
$order = Order::factory()->create();
PlaceOrder::dispatch($order);
Queue::assertPushed(SendOrderConfirmation::class, function ($job) use ($order) {
return $job->order->id === $order->id;
});
});
浏览器测试(Dusk)
test('user can complete checkout flow', function () {
$this->browse(function (Browser $browser) {
$browser->loginAs(User::factory()->create())
->visit('/cart')
->press('Checkout')
->waitForText('Order Confirmed')
->assertSee('Thank you');
});
});
Artisan 命令、迁移、填充器和工厂
迁移约定
// Always include down() for rollback capability
public function up(): void
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('number')->unique();
$table->integer('amount'); // Store money as cents
$table->string('currency', 3);
$table->string('status')->default('draft');
$table->timestamp('due_at')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['user_id', 'status']);
});
}
工厂模式
class InvoiceFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'number' => $this->faker->unique()->numerify('INV-####'),
'amount' => $this->faker->numberBetween(1000, 100000),
'currency' => 'USD',
'status' => 'draft',
'due_at' => now()->addDays(30),
];
}
public function paid(): static
{
return $this->state(fn () => ['status' => 'paid']);
}
public function overdue(): static
{
return $this->state(fn () => [
'status' => 'sent',
'due_at' => now()->subDays(7),
]);
}
}
Laravel 目录结构约定
app/
├── Actions/ # Single-purpose action classes
├── Casts/ # Custom Eloquent casts
├── Console/Commands/ # Artisan commands
├── Enums/ # PHP backed enums
├── Events/ # Event classes
├── Exceptions/ # Custom exception classes
├── Http/
│ ├── Controllers/ # Resourceful or single-action controllers
│ ├── Middleware/ # Request/response middleware
│ └── Requests/ # Form Request validation
├── Jobs/ # Queued job classes
├── Listeners/ # Event listener classes
├── Mail/ # Mailable classes
├── Models/ # Eloquent models
├── Notifications/ # Notification classes
├── Observers/ # Model observers
├── Policies/ # Authorization policies
├── Providers/ # Service providers
├── Rules/ # Custom validation rules
├── Services/ # Domain service classes
└── View/Components/ # Blade view components
database/
├── factories/ # Model factories
├── migrations/ # Schema migrations (timestamped)
└── seeders/ # Database seeders
resources/views/
├── components/ # Blade components
├── layouts/ # Layout templates
├── livewire/ # Livewire component views
└── mail/ # Email templates
routes/
├── api.php # API routes
├── channels.php # Broadcast channels
├── console.php # Artisan closures
└── web.php # Web routes
tests/
├── Feature/ # Feature (integration) tests
├── Unit/ # Unit tests
└── Browser/ # Dusk browser tests
决策表
身份验证策略
| 场景 | 推荐方案 |
|---|---|
| SPA + 同域 | Sanctum(基于 Cookie,CSRF) |
| SPA + 不同域 | Sanctum(基于令牌) |
| 移动应用 | Sanctum(基于令牌) |
| 第三方 API 使用者 | Passport(OAuth2) |
| 简单 API 令牌 | Sanctum(明文哈希) |
| 社交登录 | Socialite + Sanctum |
缓存层
| 数据类型 | 缓存驱动 | TTL | 失效方式 |
|---|---|---|---|
| 配置 / 路由 / 视图 | File(artisan cache) | 直到下次部署 | artisan optimize:clear |
| 数据库查询结果 | Redis / Memcached | 5-60 分钟 | 事件驱动或 TTL |
| 整页 / 片段 | Redis | 1-15 分钟 | 缓存标签 |
| 会话数据 | Redis | 会话生命周期 | 自动 |
| 速率限制 | Redis | 窗口时长 | 自动 |
文件存储
| 场景 | 磁盘 | 驱动 |
|---|---|---|
| 用户上传(生产环境) | s3 | Amazon S3 / 兼容服务 |
| 用户上传(本地开发) | local | 本地文件系统 |
| 公共资源 | public | 本地存储并使用符号链接 |
| 临时文件 | local | 本地存储,按计划清理 |
反模式 / 常见错误
| 反模式 | 失败原因 | 应采取的做法 |
|---|---|---|
| 控制器过于臃肿 | 业务逻辑难以测试和维护 | 将逻辑移至 Action 或 Service 类 |
| 在控制器中直接编写 SQL | 有 SQL 注入风险,且不具备可移植性 | 使用 Eloquent 或 Query Builder |
| 缺少批量赋值保护 | 存在数据篡改漏洞 | 始终定义 $fillable 或 $guarded |
| 在控制器中内联验证 | 将验证与 HTTP 层耦合 | 使用 Form Requests |
| Job 未配置重试 / 退避 | 失败无提示,且无法恢复 | 配置 $tries、$backoff、failed() |
| 过度使用全局作用域 | 隐藏的查询行为会令开发者措手不及 | 优先使用局部作用域 |
| 将金额存为浮点数 | 会出现浮点精度误差 | 使用整数表示分,在展示时转换 |
| 缺少数据库索引 | 数据量增大时查询缓慢 | 为 WHERE + ORDER BY 添加复合索引 |
| 在配置文件中存放密钥 | 凭据可能泄露到版本控制中 | 仅使用 .env |
| 使用生产数据库进行测试 | 数据可能损坏,测试结果不可靠 | 使用内存中的 SQLite 或专用测试数据库 |
| 在 API 响应中使用延迟加载 | 产生 N+1 查询,导致 API 响应缓慢 | 在开发环境中启用 preventLazyLoading() |
防止自我合理化
- 不要跳过迁移并直接修改数据库——迁移才是事实来源。
- 不要因为“这样更快”就把业务逻辑放进控制器——请使用 Action 类。
- 不要因为“验证很简单”就跳过 Form Requests——验证逻辑总会不断增长。
- 不要因为“很烦人”就禁用
preventLazyLoading()——请修复 N+1 查询。 - 不要因为“金额很小”就将金额存为浮点数——精度误差会不断累积。
集成点
| 技能 | 关联方式 |
|---|---|
php-specialist | 现代 PHP 8.x 模式是所有 Laravel 代码的基础 |
laravel-boost | AI 辅助开发指南和 MCP 工具 |
senior-backend | API 设计、缓存策略、事件驱动架构 |
test-driven-development | 使用 RED-GREEN-REFACTOR 的 Pest 测试工作流 |
database-schema-design | 迁移规划、索引策略、数据建模 |
security-review | Sanctum/Passport 配置、CSRF、输入验证 |
performance-optimization | 查询分析、缓存调优、队列 Worker 扩缩容 |
deployment | Forge/Vapor/Envoyer 部署、artisan optimize |
context7 MCP | 信息不确定时,获取最新的 Laravel 文档 |
laravel/docs GitHub | Laravel API 参考的权威来源 |
技能类型
灵活——根据工作范围调整多阶段流程。单个模型变更可以完全跳过 Phase 2,而新模块则应遵循全部五个阶段。无论工作范围如何,核心规范(预加载、Form Requests、Pest 测试、优先迁移的架构变更)都不可妥协。