php-laravel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePHP & Laravel Development
PHP & Laravel 开发
Scoped to framework-level PHP. Work on php-src internals or a native PHP extension is C, not PHP: the skill covers it, including the Zend API conventions ( arginfo, the request-scoped allocator, custom object handlers, ).
ia-c-systemsgen_stub.phpt本内容聚焦于框架级PHP开发。php-src内部实现或原生PHP扩展属于C语言范畴,而非PHP,相关内容由技能覆盖,包括Zend API规范(参数信息、请求作用域分配器、自定义对象处理器、测试文件)。
ia-c-systemsgen_stub.phptCode Style
代码风格
- in every file
declare(strict_types=1) - Happy path last -- guards and errors first, success at the end. Early returns, no .
else - Comments explain why, never what. Never comment tests. If code needs a "what" comment, rename or restructure.
- No single-letter variables -- not
$exception,$enot$request$r - not
?string. Always specifystring|null. Import classnames, never inline FQN.void - Validation uses array notation for easier custom rule classes
['required', 'email'] - PHPStan level 8+ (); aim for 9 on new projects.
phpstan analyse --level=8/@phpstan-typefor generic collection types. The missing-iterable-value-type check lands at level 6 (and every level above it), so any project at 8+ inherits it: use the generic form on every iterable --@phpstan-param,@return Collection<int, User>-- and array-shape notation@param array<int, MyObject>for fixed-key returns; a barearray{first: SomeClass, second: SomeClass}orCollectionwill not clear it.array
- 每个文件中添加
declare(strict_types=1) - 正常逻辑放在最后——先处理守卫逻辑和错误,最后处理成功场景。提前返回,不使用
else - 注释用于解释原因,而非内容。测试代码无需注释。如果代码需要注释说明内容,应重命名或重构代码
- 禁止使用单字母变量——使用而非
$exception,$e而非$request$r - 使用而非
?string。始终明确指定string|null。导入类名,绝不使用内联完全限定名(FQN)void - 验证使用数组格式,便于自定义规则类
['required', 'email'] - PHPStan等级8+(执行);新项目目标为等级9。针对泛型集合类型使用
phpstan analyse --level=8/@phpstan-type。缺失可迭代值类型检查在等级6及以上生效,因此等级8+的项目都会继承该检查:对所有可迭代类型使用泛型形式——@phpstan-param、@return Collection<int, User>——固定键返回值使用数组形状表示法@param array<int, MyObject>;仅使用array{first: SomeClass, second: SomeClass}或Collection无法通过检查array
Modern PHP (8.4)
现代PHP(8.4)
Use when applicable -- no explanatory comments for these in generated code:
- Readonly classes/properties for immutable data; constructor promotion with readonly
- Enums with methods and interfaces for domain constants
- Match expressions over switch
- First-class callable syntax
$fn = $obj->method(...) - Fibers for cooperative async when Swoole/ReactPHP not available
- DNF types for complex constraints
(Stringable&Countable)|null - Property hooks:
public string $name { get => strtoupper($this->name); set => trim($value); } - Asymmetric visibility: -- public read, private write
public private(set) string $name - without parentheses in chains:
newnew MyService()->handle() - ,
array_find(),array_any()-- native array search/check without closures wrapping Collectionarray_all()
适用时使用以下特性——生成的代码中无需添加解释注释:
- 只读类/属性用于不可变数据;结合readonly的构造函数提升
- 带方法和接口的枚举用于领域常量
- 使用Match表达式替代switch
- 一等调用语法
$fn = $obj->method(...) - 在无法使用Swoole/ReactPHP时,使用Fibers实现协作式异步
- DNF类型用于复杂约束
(Stringable&Countable)|null - 属性钩子:
public string $name { get => strtoupper($this->name); set => trim($value); } - 非对称可见性:——公共可读,私有可写
public private(set) string $name - 链式调用中省略括号的语法:
newnew MyService()->handle() - 、
array_find()、array_any()——无需通过闭包包装Collection即可实现原生数组搜索/检查array_all()
Laravel Architecture
Laravel架构
- Escalate structure only when it pays for itself. Simple CRUD → a fat Eloquent model + Form Request is correct; do not add layers. Reach for an Action class when an operation crosses model boundaries or gains a 3rd caller. Extract a non-Eloquent domain object only when a business rule needs testing without booting the DB, or protects an invariant the model can't. Default down the ladder, not up -- an unused abstraction is a defect, not foresight.
- Thin controllers -- only validate, call service/action, return response. Domain behavior (scopes, accessors, relationships) lives in models; cross-cutting orchestration in service classes.
- Never call outside
env(). Whereverconfig/has run (the deploy sequence requires it, so typically production), everyphp artisan config:cachecall outside a config file returnsenv()-- silently, with no error. Read throughnulland put third-party credentials inconfig('services.github.token')rather than inventing a new config file.config/services.php - Service classes for business logic with readonly DI:
__construct(private readonly PaymentService $payments) - Action classes (single-purpose invokable) for operations crossing service boundaries
- Form Requests for all validation -- never inline in controllers, never inside services. Add so services receive typed, pre-validated data; internal code trusts that input was validated at the boundary.
toDto() - Conditional validation: ,
Rule::requiredIf(),sometimesexclude_if - Events + Listeners for side effects (notifications, logging, cache invalidation) -- not in services. Name events past-tense in business terms (, not
OrderPlaced). Carry IDs and changed facts in the payload, not the full Eloquent model --OrderRecordUpdatedre-fetches by key when a queued listener runs, so a model passed in-memory goes stale (same desync class as the observer/stale-copy pitfall below).SerializesModels - Feature folder organization over type-based past ~20 models
- 仅当收益大于成本时才升级结构。简单CRUD场景下,使用功能完整的Eloquent模型+Form Request是正确选择;无需添加额外层级。当操作跨模型边界或有第三个调用方时,再考虑使用Action类。仅当业务规则需要在不启动数据库的情况下测试,或需要保护模型无法维护的不变量时,才提取非Eloquent领域对象。默认优先选择更简单的方案,而非过度抽象——未被使用的抽象是缺陷,而非前瞻性设计
- 精简控制器——仅负责验证、调用服务/Action、返回响应。领域行为(作用域、访问器、关联关系)放在模型中;跨领域编排放在服务类中
- 绝不在目录外调用
config/。当执行过env()(部署流程要求执行此命令,通常生产环境会执行)后,php artisan config:cache目录外的所有config调用都会静默返回env(),且无错误提示。通过null读取配置,并将第三方凭证放在config('services.github.token')中,而非创建新的配置文件config/services.php - 服务类用于包含依赖注入的业务逻辑:
__construct(private readonly PaymentService $payments) - Action类(单一用途的可调用类)用于跨服务边界的操作
- Form Requests用于所有验证——绝不在控制器内联验证,也不在服务内验证。添加方法,使服务接收已类型化、预验证的数据;内部代码信任边界处已完成输入验证
toDto() - 条件验证:、
Rule::requiredIf()、sometimesexclude_if - 事件+监听器用于处理副作用(通知、日志、缓存失效)——而非在服务中处理。事件名称使用过去式的业务术语(如,而非
OrderPlaced)。在负载中携带ID和变更信息,绝不传递完整的Eloquent模型——OrderRecordUpdated会在队列监听器运行时通过键重新获取模型,因此内存中传递的模型会过时(与下文观察者/副本过期陷阱属于同一类不同步问题)SerializesModels - 当模型数量超过20个左右时,使用按功能划分的文件夹组织方式,而非按类型划分
Production Resilience
生产环境韧性
- Fail-fast config validation in a service provider's : missing API keys, invalid DSNs, misconfigured queues crash on startup, not on the first request that hits the code path.
boot() - Health endpoints: (shallow, 200 if the process responds) and
/health(deep -- checks DB, Redis, critical services)./ready
- 快速失败的配置验证:在服务提供者的方法中验证配置——缺失的API密钥、无效的DSN、配置错误的队列应在启动时崩溃,而非在首次请求触发代码路径时崩溃
boot() - 健康检查端点:(浅度检查,进程响应则返回200)和
/health(深度检查——验证数据库、Redis、关键服务状态)/ready
Routing
路由
- Scoped route model binding to prevent cross-tenant access:
Route::scopeBindings()->group(fn() => ...) - for custom binding resolution
Route::model('conversation', AiConversation::class) - API resource routes: -- index/store/show/update/destroy without create/edit
Route::apiResource('posts', PostController::class)
- 作用域路由模型绑定,防止跨租户访问:
Route::scopeBindings()->group(fn() => ...) - 用于自定义绑定解析
Route::model('conversation', AiConversation::class) - API资源路由:——自动生成index/store/show/update/destroy路由,不含create/edit
Route::apiResource('posts', PostController::class)
Migrations
迁移
- Anonymous class migrations; plural table names matching model convention
snake_case - Foreign keys: . Always index foreign keys and frequently filtered columns.
$table->foreignId('user_id')->constrained()->cascadeOnDelete() - Down method: rollback logic or for new tables
Schema::dropIfExists() - Separate schema and data migrations -- backfills in their own migration file, not mixed with DDL
- Renames/removals use expand-contract: add new column → backfill → switch reads → drop old (full pattern in skill)
ia-postgresql - Never edit a migration that has run in a shared environment -- write a new one
- Set for per-row commit/lock-release (resumable backfills) or statements Postgres rejects inside a transaction (
public $withinTransaction = false;,CREATE INDEX CONCURRENTLY). Otherwise innerALTER TYPE ... ADD VALUEloops become savepoints, not independent commits (pitfalls-deep.md); no-op on MySQL.DB::transaction() - resets only the SQL connection -- external stores (DynamoDB, S3, Redis) persist across it, so external-store data migrations re-run on already-migrated data and must be idempotent on a second run.
migrate:fresh
- 使用匿名类迁移;表名使用复数形式,符合模型命名规范
snake_case - 外键:。始终为外键和频繁过滤的列创建索引
$table->foreignId('user_id')->constrained()->cascadeOnDelete() - Down方法:回滚逻辑或对新表使用
Schema::dropIfExists() - 分离结构迁移和数据迁移——数据回填放在单独的迁移文件中,不与DDL混合
- 重命名/删除操作使用扩展-收缩模式:添加新列→回填数据→切换读取源→删除旧列(完整模式见技能)
ia-postgresql - 绝不编辑已在共享环境中执行过的迁移——编写新的迁移文件
- 设置用于逐行提交/释放锁(可恢复的回填),或PostgreSQL不允许在事务内执行的语句(如
public $withinTransaction = false;、CREATE INDEX CONCURRENTLY)。否则内部ALTER TYPE ... ADD VALUE循环会成为保存点,而非独立提交(详见pitfalls-deep.md);MySQL环境下此设置无影响DB::transaction() - 仅重置SQL连接——外部存储(DynamoDB、S3、Redis)的数据会保留,因此外部存储的数据迁移会在已迁移的数据上重新执行,必须保证第二次执行时是幂等的
migrate:fresh
Eloquent
Eloquent
- -- catch N+1 during development
Model::preventLazyLoading(!app()->isProduction()) - Select only needed columns:
Post::with(['user:id,name'])->select(['id', 'title', 'user_id']) - Bulk operations at database level: -- never load into memory to update.
Post::where('status', 'draft')->update([...])/increment()for counters.decrement() - Composite indexes for common query combinations
- for large datasets, lazy collections for memory-constrained processing
chunk(1000) - Query scopes (,
scopeActive) for reusable constraintsscopeRecent - /
withCount('comments')-- never load relations just to countwithExists('approvals') - for conditional query building
->when($filter, fn($q) => $q->where(...)) - -- automatic rollback on exception
DB::transaction(fn() => ...) - for bulk insert-or-update
Model::upsert($rows, ['unique_key'], ['update_cols']) - /
PrunablewithMassPrunablequery for automatic stale record cleanupprunable() - is a mass assignment vulnerability -- always explicit
$guarded = []$fillable
- ——在开发阶段捕获N+1查询问题
Model::preventLazyLoading(!app()->isProduction()) - 仅选择所需列:
Post::with(['user:id,name'])->select(['id', 'title', 'user_id']) - 在数据库层面执行批量操作:——绝不加载到内存中更新。使用
Post::where('status', 'draft')->update([...])/increment()处理计数器decrement() - 为常用查询组合创建复合索引
- 处理大型数据集时使用,内存受限场景使用惰性集合
chunk(1000) - 查询作用域(、
scopeActive)用于可复用的约束条件scopeRecent - 使用/
withCount('comments')——绝不加载关联关系仅用于计数withExists('approvals') - 使用构建条件查询
->when($filter, fn($q) => $q->where(...)) - ——异常时自动回滚
DB::transaction(fn() => ...) - 用于批量插入或更新
Model::upsert($rows, ['unique_key'], ['update_cols']) - 使用/
Prunable结合MassPrunable查询自动清理过期记录prunable() - 存在批量赋值漏洞——始终显式设置
$guarded = []$fillable
API Resources
API资源
- for relationships -- prevents N+1 in responses
whenLoaded() - /
when()for permission-based fields;mergeWhen()for pivot datawhenPivotLoaded() - for custom headers,
withResponse()for metadata (version, pagination)with()
- 使用处理关联关系——防止响应中的N+1查询问题
whenLoaded() - 使用/
when()基于权限返回字段;使用mergeWhen()处理中间表数据whenPivotLoaded() - 使用设置自定义响应头,
withResponse()添加元数据(版本、分页信息)with()
API Design
API设计
- Contract-first: define the API Resource (response contract) and Form Request (input contract) before writing the controller.
- Never return raw models or from controllers -- Resources control exactly what's serialized. Every observable field, ordering, or timing becomes a caller dependency (Hyrum's Law).
toArray() - Add, don't modify: new fields/endpoints over changing or removing existing ones. Deprecate first (in OpenAPI/docblock), remove in a later version.
@deprecated - Consistent envelope: . Normalize
{ "success": bool, "data": ..., "error": null, "meta": {} },ValidationException,ModelNotFoundException, and application errors toAuthorizationExceptionin the exception handler -- callers build error handling once.{ "success": false, "error": { "code": "...", "message": "..." } } - Isolate third-party SDKs behind an adapter class. Catch vendor exceptions (,
GuzzleHttp\Exception\ClientException) inside the adapter and rethrow as domain exceptions (Stripe\Exception\*) -- never let a Guzzle/Stripe exception bubble into a controller or service.PaymentFailedException - Never return the raw vendor object (, a Guzzle
Stripe\Charge) from an adapter -- map it to a DTO first. Otherwise every vendor field becomes a caller dependency (Hyrum's Law), same as returning raw models on egress.Response - Third-party responses are untrusted data: validate shape and content through the DTO before use in logic or rendering. Inject the specific client/credentials the adapter needs, not the whole config or container.
- 契约优先:在编写控制器之前,先定义API资源(响应契约)和Form Request(输入契约)
- 绝不从控制器返回原始模型或结果——资源类严格控制序列化内容。每个可见字段、排序方式或响应时机都会成为调用方的依赖(海勒姆定律)
toArray() - 新增而非修改:优先添加新字段/端点,而非修改或删除现有内容。先标记弃用(在OpenAPI/文档块中添加),再在后续版本中删除
@deprecated - 统一响应格式:。在异常处理器中将
{ "success": bool, "data": ..., "error": null, "meta": {} }、ValidationException、ModelNotFoundException和应用程序错误统一格式化为AuthorizationException——调用方只需实现一次错误处理逻辑{ "success": false, "error": { "code": "...", "message": "..." } } - 将第三方SDK隔离在适配器类之后。在适配器内部捕获厂商异常(如、
GuzzleHttp\\Exception\\ClientException),并重新抛出为领域异常(如Stripe\\Exception\\*)——绝不让Guzzle/Stripe异常冒泡到控制器或服务中PaymentFailedException - 绝不返回原始厂商对象(如、Guzzle的
Stripe\\Charge)——先映射为DTO再返回。否则每个厂商字段都会成为调用方的依赖(海勒姆定律),与返回原始模型的问题相同Response - 第三方响应为不可信数据:在逻辑处理或渲染之前,通过DTO验证其形状和内容。注入适配器所需的特定客户端/凭证,而非整个配置或容器
Queues & Jobs
队列与任务
- Batching: ; chaining:
Bus::batch([...])->then()->catch()->finally()->dispatch()Bus::chain([new Step1, new Step2])->dispatch() - Rate limiting:
Redis::throttle('api')->allow(10)->every(60)->then(fn() => ...) - interface to prevent duplicate processing -- it is a de-duplication hint, not an at-least-once guarantee. When the lock is already held the dispatch is silently discarded: no job queued, no exception, no log line, and
ShouldBeUniquereturns normally. Where the skip is user-visible (a re-clicked "regenerate report" that produces nothing), check the lock before dispatching and surface the state. Adispatch()event exists on theIlluminate\Queue\Events\UniqueJobSkippedbranch but had not landed in a tagged release as of 13.24 -- confirm it is in the installed version before listening for it13.x - Always handle failures -- implement on jobs
failed()
- 批处理:;链式处理:
Bus::batch([...])->then()->catch()->finally()->dispatch()Bus::chain([new Step1, new Step2])->dispatch() - 速率限制:
Redis::throttle('api')->allow(10)->every(60)->then(fn() => ...) - 接口用于防止重复处理——这是去重提示,而非至少一次处理的保证。当锁已被持有时,任务调度会被静默丢弃:不会进入队列,无异常,无日志行,且
ShouldBeUnique会正常返回。当跳过操作对用户可见时(如重复点击“重新生成报告”却无结果),应在调度前检查锁状态并提示用户。dispatch()事件在13.x分支中存在,但截至13.24版本尚未发布正式版——监听前请确认已安装的版本包含该事件Illuminate\\Queue\\Events\\UniqueJobSkipped - 始终处理失败场景——在任务类中实现方法
failed()
Testing (PHPUnit)
测试(PHPUnit)
Diagnosing failing tests
诊断失败的测试
- Run the single failing test in isolation () before reading app code.
phpunit --filter test_name - Passes solo but fails in the suite → suspect shared state: container singletons, statics, residue, DB state leaking between tests (the classic paratest failure).
Carbon::setTestNow() - Diff expected vs actual output before hypothesizing a cause.
- Decide explicitly: test-bug or code-bug. Name which before editing either.
- Never weaken an assertion to make it pass.
Test throws → strict mode () + factory omits a column with a DB default: add the column to the factory or after create.
MissingAttributeExceptionModel::shouldBeStrict()->refresh()- 在查看应用代码之前,先单独运行失败的测试()
phpunit --filter test_name - 单独运行通过但套件运行失败→怀疑共享状态:容器单例、静态变量、残留、测试之间的数据库状态泄漏(典型的paratest失败场景)
Carbon::setTestNow() - 在假设原因之前,先对比预期输出与实际输出的差异
- 明确判断:测试错误还是代码错误。在修改任何一方之前先确定问题类型
- 绝不弱化断言使其通过
测试抛出→严格模式()+工厂遗漏了带有数据库默认值的列:将列添加到工厂中,或在创建后调用
MissingAttributeExceptionModel::shouldBeStrict()->refresh()Patterns
模式
- Feature tests (): HTTP through the full stack (
tests/Feature/,getJson()) -- default for anything touching routes, controllers, or models. Unit tests (postJson()): isolated services, actions, value objects.tests/Unit/ - for full migration reset per test;
RefreshDatabasefor transaction-wrap (faster, no migration testing);DatabaseTransactionsto run and rollback per testDatabaseMigrations - Model factories for all test data -- never raw inserts
DB::table() - One behavior per test. Name with prefix:
test_test_user_can_update_own_profile - Assert both response status AND side effects (DB state, jobs, notifications): /
assertDatabaseHasassertDatabaseMissing - for auth,
actingAs($user)for API authSanctum::actingAs($user, ['ability']) - Fake facades BEFORE the action: → act →
Queue::fake(); same forQueue::assertPushed(...)→Http::fake(['host/*' => Http::response(...)])Http::assertSent(...) - for authorization assertions
Gate::forUser($user)->allows('update', $post) - Coverage target: 80%+ with or
pcovin CIXDEBUG_MODE=coverage
Generic test discipline (anti-patterns, mock rules, rationalization resistance): skill. Laravel testing deep dives: see References below.
ia-writing-tests- 功能测试():通过完整栈发送HTTP请求(
tests/Feature/、getJson())——适用于任何涉及路由、控制器或模型的场景。单元测试(postJson()):测试隔离的服务、Action、值对象tests/Unit/ - :每个测试重置完整迁移;
RefreshDatabase:使用事务包裹(速度更快,不测试迁移);DatabaseTransactions:每个测试运行并回滚迁移DatabaseMigrations - 所有测试数据使用模型工厂——绝不使用原始插入
DB::table() - 每个测试验证一个行为。名称以前缀开头:
test_test_user_can_update_own_profile - 同时断言响应状态和副作用(数据库状态、任务、通知):/
assertDatabaseHasassertDatabaseMissing - 使用模拟认证,
actingAs($user)用于API认证Sanctum::actingAs($user, ['ability']) - 在执行操作之前模拟门面:→执行操作→
Queue::fake();Queue::assertPushed(...)→Http::fake(['host/*' => Http::response(...)])同理Http::assertSent(...) - 使用断言授权
Gate::forUser($user)->allows('update', $post) - 覆盖率目标:80%+,在CI中使用或
pcovXDEBUG_MODE=coverage
通用测试规范(反模式、模拟规则、避免合理化):技能。Laravel测试深度解析:见下文参考资料
ia-writing-testsCommon Pitfalls
常见陷阱
Real production footguns, invisible to PHPStan and feature tests alone. Extended mechanics and alternatives in pitfalls-deep.md.
Query-builder silently skips observers and audit events. and fire no model events -- observers, Auditable traits, all bypassed. Fix: in a transaction keeps events firing; raw mass update only with a comment.
update()Model::query()->where(...)->update([...])Relation::update()static::saving/updatinglockForUpdate() + save()// intentionally bypasses <Observer>Observer cleanup at parent scope nukes siblings. on a single child delete wipes storage for all siblings while their rows still point at the keys. Detection: when a single-row has an Observer, check whether its hooks operate at parent or row scope. Fix: scope cleanup to the row's own paths, or move it to an Action that knows the sibling count.
deleting()Storage::deleteDirectory($parent->uploadPath)delete()chunkById + json_decode + mutate + json_encode + updateDB::raw("jsonb_set(...)")lockForUpdate()date:<fmt>$model->toArray()JsonResource::resolve()toArray()json_encode($model)Nested-array validation accepts scalar elements when only rules are set. does not enforce that each is an array -- scalars pass, then yields (blank row) or a (500). Always pair per-key rules with .
*.field'items.*.name' => 'string'items.*$data['items'][0]['name']nullTypeError'items.*' => 'array'DB::afterCommittriesfailed()Observer writes a model the caller also holds → stale in-memory copy; the caller's later silently re-clobbers. Fix: after the triggering event, or when the caller owns the column.
save()$model->refresh()Model::withoutEvents()BelongsToMany::attachdetachsyncupdateExistingPivotPivot->using(PivotModel::class)firstOrCreate(...)->fill([...])->save()Carbon::parse('2020')HHMMbefore_or_equal:todayafterbeforeCarbon::createFromFormat('Y', $year)->startOfYear()这些是实际生产环境中的隐患,仅靠PHPStan和功能测试无法发现。详细机制及替代方案见pitfalls-deep.md
查询构建器会静默跳过观察者和审计事件。和不会触发任何模型事件——观察者、Auditable trait、都会被绕过。修复方案:在事务中使用以保留事件触发;仅在添加注释时使用原始批量更新
update()Model::query()->where(...)->update([...])Relation::update()static::saving/updatinglockForUpdate() + save()// intentionally bypasses <Observer>观察者中的父级范围清理会删除兄弟项。在单个子项删除时执行会删除所有兄弟项的存储,而它们的数据库记录仍指向这些键。检测方法:当单行有观察者时,检查其钩子是在父级范围还是行级范围操作。修复方案:将清理范围限定到行自身的路径,或移至了解兄弟项数量的Action中
deleting()Storage::deleteDirectory($parent->uploadPath)delete()chunkById + json_decode + 修改 + json_encode + updateDB::raw("jsonb_set(...)")lockForUpdate()date:<fmt>$model->toArray()JsonResource::resolve()toArray()json_encode($model)嵌套数组验证在仅设置规则时会接受标量元素。不会强制每个是数组——标量会通过验证,然后会返回(空行)或抛出(500错误)。始终将逐键规则与配对使用
*.field'items.*.name' => 'string'items.*$data['items'][0]['name']nullTypeError'items.*' => 'array'DB::afterCommittriesfailed()观察者修改了调用方持有的模型→内存副本过期;调用方后续的会静默覆盖修改。修复方案:触发事件后调用,或当调用方拥有列的控制权时使用
save()$model->refresh()Model::withoutEvents()BelongsToMany::attachdetachsyncupdateExistingPivotPivot->using(PivotModel::class)firstOrCreate(...)->fill([...])->save()Carbon::parse('2020')HHMMbefore_or_equal:todayafterbeforeCarbon::createFromFormat('Y', $year)->startOfYear()Discipline
规范
- Simplicity first -- every change as simple as possible, minimal code impact
- Only touch what's necessary -- no unrelated changes
- No hacky workarounds -- if a fix feels wrong, step back and implement the clean solution
- New abstraction requires 3+ usage sites; otherwise inline it
- No empty catch blocks -- log or rethrow, never swallow
- Verify before declaring done: with zero warnings
./vendor/bin/phpstan analyse --level=8 && ./vendor/bin/phpunit
- 优先保持简单——每个变更尽可能简单,代码影响最小化
- 仅修改必要部分——不进行无关变更
- 不使用临时解决方案——如果修复方案感觉不对,退一步实现干净的解决方案
- 新抽象需要3个以上的使用场景;否则内联实现
- 禁止空catch块——记录日志或重新抛出,绝不吞掉异常
- 完成前验证:执行,确保零警告
./vendor/bin/phpstan analyse --level=8 && ./vendor/bin/phpunit
Production Performance
生产环境性能
OPcache + JIT + preloading configuration and Laravel deploy caches (, , etc.): production-performance.md
config:cacheroute:cacheOPcache + JIT + 预加载配置及Laravel部署缓存(、等):production-performance.md
config:cacheroute:cacheReferences
参考资料
- laravel-ecosystem.md -- Notifications, Task Scheduling, Custom Casts
- testing.md -- PHPUnit essentials, data providers, running tests
- feature-testing.md -- Auth, validation, API, console, DB assertions
- mocking-and-faking.md -- Facade fakes, action mocking, Mockery
- factories.md -- States, relationships, sequences, afterCreating hooks
- production-performance.md -- OPcache, JIT, preloading, deploy caches
- pitfalls-deep.md -- afterCommit alternatives, observer-desync mechanics, jsonb race, savepoint mechanics
- laravel-ecosystem.md——通知、任务调度、自定义转换
- testing.md——PHPUnit基础、数据提供者、运行测试
- feature-testing.md——认证、验证、API、控制台、数据库断言
- mocking-and-faking.md——门面模拟、Action模拟、Mockery
- factories.md——状态、关联关系、序列、afterCreating钩子
- production-performance.md——OPcache、JIT、预加载、部署缓存
- pitfalls-deep.md——afterCommit替代方案、观察者不同步机制、jsonb竞争、保存点机制",