Coolify 仓库中的 Laravel 最佳实践技能:19 类规则体系与源码印证指南
Coolify 是一个基于 Laravel 构建的开源自托管 PaaS,其仓库中内置了一套完整的 AI 辅助开发技能文件 .agents/skills/laravel-best-practices/SKILL.md,系统性地总结了 19 大类 Laravel 后端最佳实践。读完本文,你将掌握这套按影响力排序的规则体系——包括数据库 N+1 治理、队列重试与锁机制、缓存分层策略、安全边界与任务调度的完整要点,并能看到 Coolify 仓库自身的配置与 Job 实现如何印证这些规则。
一、技能文件结构与"一致性优先"原则
这份技能文件的 frontmatter 声明了触发条件:凡涉及控制器、模型、迁移、Form Request、Policy、Job、定时命令、Service 类的编写、审查或重构,均适用该技能。其正文结构分为三部分:
- Consistency First(一致性优先):应用任何规则之前,先检查代码库已有的做法。Laravel 往往存在多种合法写法——最佳选择是代码库已经采用的那一种,即使另一种模式理论上更优。"不一致性比次优模式更糟"。因此这些规则是"尚无可循模式时的默认值",而非推翻既有约定的强制覆盖。
- Quick Reference(快速参考):19 类规则目录,每类都链接到 rules/ 目录下的详细规则文件,并附要点清单。
- How to Apply(应用方法):按文件类型选取相关章节(如迁移 → §16、控制器 → §1/§3/§5/§6/§10)、先查兄弟文件的既有模式、再按已安装版本核对 API 语法。
下表是该快速参考目录的完整映射(已转换为仓库根目录相对路径):
| 编号 | 主题 | 规则文件 |
|---|---|---|
| 1 | 数据库性能 | db-performance.md |
| 2 | 高级查询模式 | advanced-queries.md |
| 3 | 安全 | security.md |
| 4 | 缓存 | caching.md |
| 5 | Eloquent 模式 | eloquent.md |
| 6 | 验证与表单 | validation.md |
| 7 | 配置 | config.md |
| 8 | 测试模式 | testing.md |
| 9 | 队列与 Job | queue-jobs.md |
| 10 | 路由与控制器 | routing.md |
| 11 | HTTP Client | http-client.md |
| 12 | 事件、通知与邮件 | events-notifications.md、mail.md |
| 13 | 错误处理 | error-handling.md |
| 14 | 任务调度 | scheduling.md |
| 15 | 架构 | architecture.md |
| 16 | 迁移 | migrations.md |
| 17 | 集合 | collections.md |
| 18 | Blade 与视图 | blade-views.md |
| 19 | 约定与风格 | style.md |
二、数据库性能:N+1 治理与批量处理(§1)
db-performance.md 是 19 类规则中第一条,也是 Coolify 这类管理面板类项目最直接影响响应时间的领域。核心要点包括:
2.1 始终使用 with() 预加载关系
懒加载在循环中触发 N+1 查询(1 条主查询 + N 条关系查询)。正确写法是一次预加载,总共只执行 2 条查询:
// 错误(N+1,执行 1 + N 条查询)
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name;
}
// 正确(共 2 条查询)
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name;
}
预加载时还可约束约束条件,只选择需要的列——务必包含外键列,否则关系无法匹配:
$users = User::with(['posts' => function ($query) {
$query->select('id', 'user_id', 'title')
->where('published', true)
->latest()
->limit(10);
}])->get();
2.2 开发环境开启懒加载防护
在 AppServiceProvider::boot() 中启用以下代码,开发阶段访问未预加载关系时抛出 LazyLoadingViolationException,让 N+1 在开发期即被暴露:
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}
2.3 分块处理与游标迭代
- 批量处理绝不
::all()加载数千条记录,用chunk(200, ...); - 迭代中修改/删除记录时必须用
chunkById()——普通chunk()基于 OFFSET,行变动后会发生偏移漏处理:
User::where('active', false)->chunkById(200, function ($users) {
$users->each->delete();
});
- 只读大结果集迭代用
cursor()(PHP generator 逐条读取,内存高效); - 计数关系用
withCount()而非加载整个集合再->count(),条件计数写法:
$posts = Post::withCount([
'comments',
'comments as approved_comments_count' => function ($query) {
$query->where('approved', true);
},
])->get();
- Blade 模板中禁止执行查询——数据一律从控制器传入。
2.4 索引策略
对出现在 WHERE、ORDER BY、JOIN、GROUP BY 中的列建索引;对常见查询模式(如 WHERE status = ? ORDER BY created_at)建复合索引。Coolify 自己的数据库迁移遵循同样思路,大量外键使用 foreignId(...)->constrained() 自动建索引,例如 2023_03_24_140711_create_servers_table.php。
三、高级查询模式(§2)
advanced-queries.md 提供了 8 个进阶技巧,解决"预加载整个 has-many 只为取一个值"这类低效模式:
addSelect()相关子查询:为 has-many 关系取单值(如最近登录时间)时,直接注入主查询,零额外查询,并可用withCasts()附带类型转换;- 子查询 FK + 虚拟
belongsTo:先用子查询取last_login_id,再在模型上定义belongsTo,获得完全水合的关联模型而非整个集合; - 条件聚合替代多次 count:
selectRaw("count(case when status = 'X' then 1 end)")一条查询完成 N 个状态计数,toBase()跳过模型水合; setRelation()打断循环 N+1:父模型已随子集合加载后,$children->each->setRelation('parent', $parent)阻止 Eloquent 再发 N 条查询;whereIn+ 子查询优于whereHas:whereHas发出逐行重执行的关联EXISTS子查询,而whereIn('company_id', Company::where(...)->select('id'))允许数据库走索引查找;- 两条简单查询可胜一条复杂查询:当二级查询高选择性且有独立索引时,多一次往返是划算的;
- 复合索引列序须与
ORDER BY一致——单列索引无法组合用于多列排序,数据库只能 filesort; - has-many 排序用相关子查询而非 join(join 会复制行):
public function scopeOrderByLastLogin($query): void
{
$query->orderByDesc(Login::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
->take(1)
);
}
四、安全规则(§3)
security.md 覆盖了 9 个安全主题,每条都可直接作为代码评审检查项:
- 批量赋值防护:每个模型必须定义
$fillable或$guarded;接受用户输入的模型禁用$guarded = []; - 每次操作都授权:控制器中用
Gate::authorize('update', $post),或在 Form Request 的authorize()方法中$this->user()->can('update', $this->route('post'))。Coolify 的 app/Policies/ 目录实现了 27 个 Policy 类(如 ApplicationPolicy.php、ServerPolicy.php),正是该规则在真实项目中的落地形态; - 防 SQL 注入:只用参数绑定,
User::whereRaw('LOWER(name) = ?', [strtolower($request->name)]); - 防 XSS:输出用
{{ }},{!! !!}仅限可信的、已净化的内容; - CSRF:所有 POST/PUT/DELETE 的 Blade 表单包含
@csrf(Inertia 应用自动处理); - 限流:认证与 API 路由套
throttle,RateLimiter::for('login', fn (Request $request) => Limit::perMinute(5)->by($request->ip())); - 文件上传校验:
mimes查扩展名、mimetypes查真实 MIME、max限制大小,存储时用生成的文件名; - 密钥管理:
.env绝不入库,应用代码中只通过config()访问密钥,env()仅允许出现在 config 文件中; - 敏感字段加密:API key/token 用
encryptedcast 并标记hidden,同时建议 CI 中定期执行composer audit。Coolify 的 EncryptedArrayCast.php 与私钥加密迁移(2024_09_16_111428_encrypt_existing_private_keys.php)说明项目本身也在落实这一条。
五、缓存分层策略(§4)
caching.md 给出了一套从请求内到跨请求的缓存层次:
| API | 适用场景 | 关键行为 |
|---|---|---|
Cache::remember() |
通用 cache-aside | 替代手工 get/put 样板代码 |
Cache::flexible('key', [300, 600], fn) |
高流量键的 stale-while-revalidate | 5 分钟内新鲜,最长 10 分钟提供稍旧数据,后台刷新,避免缓存击穿时某用户必然慢响应 |
Cache::memo() |
单请求内重复读同一键 | 内存驻留,5 次调用 = 1 次 Redis 往返 |
| Cache tags | 成组失效 | Cache::tags(['user-1'])->flush();仅 redis/memcached/dynamodb 驱动支持 |
Cache::add() |
原子条件写 | 仅当键不存在时写入,消除检查-写入竞态 |
once() |
纯内存的每请求/每对象记忆化 | 完全不触碰缓存存储 |
Cache::lock() / lockForUpdate() |
竞态条件 | 分布式锁 |
| Failover store | 生产环境 | 'failover' => ['driver' => 'failover', 'stores' => ['redis', 'database']],Redis 宕机自动降级 |
once() 与 Cache::memo() 的选型口诀:只做昂贵计算的一次性缓存用 once();既想跨请求又想减少往返用 Cache::memo()。
六、队列与 Job:Coolify 配置中的实证(§9)
queue-jobs.md 的规则与 Coolify 仓库的实际配置高度呼应,是本文最适合"规则—源码互证"的一节。
规则要点:
retry_after必须大于 Job 的timeout,否则 worker 会在任务仍在运行时重新派发,造成重复执行;重试间隔用指数退避$backoff = [1, 5, 10];ShouldBeUnique防重复处理,uniqueId()返回业务唯一键;ShouldBeUniqueUntilProcessing则在处理开始时即释放锁,允许新实例入队;- 必须实现
failed()显式处理终态失败,不能静默丢失; - 使用
retryUntil()做时间上限重试时必须设$tries = 0; - 调用第三方 API 的 Job 套
RateLimited中间件;相关 Job 用Bus::batch()整体成败。
Coolify 中的印证:
- app/Jobs/CleanupHelperContainersJob.php 声明了
implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue——一个同时加密载荷、防重复执行的清理 Job,正是规则中ShouldBeUnique的典型应用场景;同类还有 CleanupInstanceStuffsJob.php 等; - config/queue.php 中
database与beanstalkd连接均为retry_after => 90,而redis连接为retry_after => 86400——不同驱动下该参数按最长 Job 运行时长取值,体现了"retry_after > 任何 timeout"的约束; - 项目启用了 Horizon:config/horizon.php 定义了 supervisor,
maxProcesses由HORIZON_MAX_PROCESSES(默认 4)控制并配有balance/balanceMaxShift/balanceCooldown自动扩缩参数,对应规则中"复杂多队列场景用 Horizon"的建议。
七、Eloquent、验证、路由:三条高频规则线(§5/§6/§10)
7.1 Eloquent 模式
eloquent.md 的要点:关系方法带正确返回类型提示(public function comments(): HasMany);可复用约束提取为 local scope(scopeActive),全局 scope 仅限软删除、多租户等普适约束且必须记录其存在;属性类型转换集中在 casts() 方法中;日期列必须 cast 为 datetime,模板直接用 Carbon 实例而非手工格式化;Post::whereBelongsTo($user) 替代手写下划线外键;查询中禁止硬编码表名字符串——必须用 (new User)->getTable(),迁移文件是唯一例外(迁移是冻结快照,引用日后可能被重命名的模型反而会坏掉)。
7.2 验证与表单
validation.md 五条规则:校验逻辑从控制器抽到 Form Request 类;新代码优先数组语法 ['required', 'email'](但先遵循项目既有风格);只允许 $request->validated(),绝不用 $request->all() 做批量赋值;条件校验用 Rule::when();跨字段自定义校验用 after() 方法返回闭包数组而非 withValidator()。Coolify 中对应的实践可见 app/Rules/ 下的 12 个自定义验证规则(如 DockerImageFormat.php、SafeWebhookUrl.php)。
7.3 路由与控制器
routing.md:隐式路由模型绑定(public function show(Post $post) 取代 findOrFail);嵌套资源用 ->scopeBindings() 强制父子从属;RESTful 端点用 Route::resource() / apiResource();控制器方法保持 10 行以内,业务逻辑抽到 Action/Service 类;Form Request 类型提示会在方法执行前自动触发验证与授权。Coolify 的路由层即按此组织:routes/api.php、routes/web.php 分别承载 API 与 Web 入口,控制器集中在 app/Http/Controllers/(48 个控制器文件)。
八、HTTP Client 与任务调度(§11/§14)
8.1 HTTP Client
http-client.md 对每个出站请求的要求:
- 显式超时:默认 30 秒对多数 API 调用太长,
Http::timeout(5)->connectTimeout(3)快速失败;服务级客户端用 macro 固化配置; - 指数退避重试:
Http::retry([100, 500, 1000]),或仅对连接异常/5xx 重试; - 显式错误处理:HTTP Client 默认不抛 4xx/5xx,用
->throw()或手动检查successful()/notFound(); Http::pool()并发独立请求:三个接口并行发出而非串行;- 测试中
Http::fake()+preventStrayRequests():禁止真实出站,并覆盖Http::failedConnection()失败场景。
8.2 任务调度
scheduling.md 六条规则:时长不定的任务加 withoutOverlapping() 防止同任务双开;多服务器部署加 onOneServer()(依赖共享缓存驱动);runInBackground() 让同 tick 的慢任务不阻塞后续任务;environments(['production']) 防止生产专用任务(计费、报表)在 staging 误跑;takeUntilTimeout() 给处理无界游标的任务设时间上界;重复的 ->onOneServer()->timezone(...) 配置收敛到 Schedule::daily()->group(...) 调度分组。
九、其余规则要点与"应用方法"
Quick Reference 中还覆盖以下主题,各自在 rules/ 目录下有独立细则:
- 配置(§7):
env()只出现在 config 文件中;环境判断用App::environment()/app()->isProduction();文案走 config、语言包或常量,不硬编码。Coolify 的 config/ 目录含 30 余个配置文件(app.php、queue.php、horizon.php 等),env()调用均收敛其中; - 测试模式(§8):
LazilyRefreshDatabase优于RefreshDatabase、assertModelExists()、工厂 state/sequence、fake 类(Event::fake()等)须在工厂数据准备之后调用、recycle()共享关联实例。Coolify 的测试体系位于 tests/(Pest 框架,Feature 测试 500+ 个文件); - 架构(§15):单一职责的 Action 类、依赖注入优先于
app()helper、默认ORDER BY id DESC或created_at DESC、UTF-8 安全用mb_*函数、defer()处理响应后工作、Context存请求级数据、Concurrency::run()并行执行; - 迁移(§16):
php artisan make:migration生成、constrained()建外键、绝不修改已上生产的迁移、索引随迁移加入而非事后补、列默认值与模型$attributes镜像一致、down()默认可逆(故意不可逆的变更用前向修复迁移)、一个迁移只处理一件事——绝不混 DDL 与 DML; - 集合(§17):高阶消息处理简单集合操作、按是否需要关系在
cursor()与lazy()间选择、迭代中更新记录用lazyById()、批量操作用toQuery(); - Blade 与视图(§18):组件模板中
$attributes->merge()、Blade 组件优于@include、@pushOnce管理按组件脚本、View Composer 共享视图数据、@aware传递深层组件 props; - 风格(§19):遵循 Laravel 命名约定、优先
Str/Arr/Number/Uri/Str::of()等官方 helper 而非裸 PHP 函数、Blade 中不写 JS/CSS、PHP 类中不嵌 HTML。
应用方法(来自 SKILL.md 末尾):识别文件类型后只选取相关章节(迁移 → §16,控制器 → §1、§3、§5、§6、§10);先查兄弟文件的既有模式并按"一致性优先"遵循;最后按已安装的 Laravel 版本核对确切 API 语法。
十、结语:把规则体系落到代码库
这份技能文件的价值在于两点:一是按"影响力"而非字母序组织 19 类规则,每条都给出"做什么 + 为什么"的正反例;二是它明确自身定位为"无既有模式时的默认值",避免与真实代码库的既有约定冲突。对 Coolify 这样的 Laravel 项目而言,config/queue.php 中分驱动的 retry_after 设置、config/horizon.php 的 supervisor 自动扩缩、Job 类上的 ShouldBeUnique 实现,都是规则与工程实践相互印证的直接证据——读者可以沿文中路径对照仓库源码,把这套规则用作评审清单或重构依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00