12 KiB
Detection Checklist
Every dimension here is a genuine fork: Laravel offers two or more valid approaches, the app's choice changes what the next agent writes, and no active project tool can pick for you. Left out on purpose: pure formatting (Pint owns it), any form an installed and enabled Rector rule rewrites to one canonical shape ($casts to casts(), $fillable to attributes, pipe-string rules to arrays, named to anonymous migrations, $signature to #[Signature]), and framework defaults any agent writes unprompted (ShouldQueue jobs, relation return types, HasFactory).
Each item gives the fork, then a hint (a grep or dir to spot which side the app takes). Hints are only a start. Read the matched files, never record on a raw count. Apply the ground rules to every verdict: a consistent choice that is a default or a tool's target form is not a pattern. Rows tagged (architecture) are the highest-signal, so record presence and deliberate absence.
A. Validation & HTTP input
- Validation entry point: inline
$request->validate()vs Form Request classes vsValidator::make().- Hint:
ls app/Http/Requests; grep->validate(/Validator::make(inapp/Http/Controllers.
- Hint:
- Custom rule location: invokable rule objects in
app/Rulesvs inline closures vsValidator::extend()in a provider. Rule objects are the defaultmake:rulepath, so record only if the app leans on closures orValidator::extendinstead. "No rule objects" alone is just no-signal.- Hint:
ls app/Rules; grepValidator::extendinapp/Providers.
- Hint:
- Typed input retrieval: typed getters (
$request->string(),->integer(),->enum(),->date()) vs raw$request->input()/ dynamic properties.- Hint: grep
->string(/->integer(/->enum(vs->input(inapp/Http.
- Hint: grep
- Custom messages/attributes:
lang/*/validation.phpvs Form Requestmessages()/attributes()methods.- Hint:
ls lang; grepfunction messages,function attributesinapp/Http/Requests.
- Hint:
B. Controllers & routing
- Controller shape: invokable single-action (
__invoke) vs resource controllers vs plain multi-method.- Hint: grep
__invokein controllers;Route::resource/apiResourcevs verb routes.
- Hint: grep
- Business-logic location (architecture): fat controllers vs delegated to Actions / Services / Jobs.
- Hint: read a few controller methods;
ls app/Actions app/Services.
- Hint: read a few controller methods;
- Route handler style: closures in
routes/*.phpvs controller classes.- Hint: count
function ()vs::classinroutes/web.php,routes/api.php.
- Hint: count
- Middleware assignment: route/group
->middleware()vs controllerHasMiddleware::middleware()vs#[Middleware]attribute.- Hint: grep
implements HasMiddleware,#[Middleware(in controllers vs->middleware(in routes.
- Hint: grep
- Route model binding: implicit (type-hinted models) vs explicit
Route::bindvs manualfindOrFail.- Hint: typed model params in signatures vs
findOrFail(in controllers; grepRoute::bind.
- Hint: typed model params in signatures vs
- Rate limiting: named
RateLimiter::for()+throttle:namevs inlinethrottle:60,1.- Hint: grep
RateLimiter::forin providers vsthrottle:in route files.
- Hint: grep
C. Authorization
- Authorization home: Gates (
Gate::define) vs Policy classes inapp/Policies.- Hint:
ls app/Policies; grepGate::defineinapp/Providers.
- Hint:
- Authorization call site:
$this->authorize()/Gate::authorize()vs$user->can()vscanmiddleware vs#[Authorize]vs@canin Blade.- Hint: grep
authorize(,->can(,middleware('can:,#[Authorize(,@can(.
- Hint: grep
D. Eloquent & models
- Mass assignment:
$fillableallow-list vs$guardedblock-list.- Hint: grep
protected $fillable/protected $guardedinapp/Models.
- Hint: grep
- Accessors/mutators: modern
Attributeclass vs legacygetXxxAttribute()/setXxxAttribute(). Record a legacy hold, it goes against the tool's grain.- Hint: grep
: Attribute/Attribute::makevsfunction get[A-Z].*Attributeinapp/Models.
- Hint: grep
- Primary keys: auto-increment vs
HasUuidsvsHasUlids.- Hint: grep
HasUuids/HasUlidsinapp/Models; migrationid()vsuuid('id').
- Hint: grep
- Custom casts: dedicated
CastsAttributesclasses (app/Casts) vs inlineAttributevs built-in cast strings.- Hint:
ls app/Casts; grepCast::class,AsStringable::classin models.
- Hint:
- Data/query layer (architecture): Eloquent directly in controllers vs repositories vs dedicated query objects (e.g. classes exposing
builder(): Builder).- Hint:
ls app/Repositories app/Queries; see where non-trivial queries are built.
- Hint:
- Query scopes: local
scope/#[Scope]methods vs dedicated builder classes.- Hint: grep
function scope/#[Scope]in models;ls app/*/Builders.
- Hint: grep
- Model events: observers (
app/Observers,#[ObservedBy]) vsbooted()closures vs event classes.- Hint:
ls app/Observers; grepbooted,::observe,#[ObservedBy].
- Hint:
- Eager-load posture: explicit per-query
->with()vs model-level$withdefaults. TreatpreventLazyLoading()separately as a development guard because it can complement either posture.- Hint: grep
protected $with,->with(, and separatelypreventLazyLoadinginapp/.
- Hint: grep
E. Architecture & organization
- Action/Service structure (architecture): Action classes (invoked via
handle/execute/__invoke) vs service objects vs neither. Cross-check the Step 0app/map: anyActions/Services/Pipelines/Jobs-as-actions folder is this pattern, so record how it is invoked.- Hint:
ls app/(the whole tree, not justActions/Services); grep the invocation method in the folder you find.
- Hint:
- DTOs (architecture): spatie/laravel-data vs plain readonly classes vs arrays everywhere.
- Hint:
ls app/Data; grepextends Data,readonly classinapp/.
- Hint:
- Dependency acquisition: constructor/method injection vs
app()/resolve()/App::make()service location.- Hint: grep
app(/resolve(/::make(inapp/vs promoted constructor deps.
- Hint: grep
- Decoupling: events + listeners vs direct service calls.
- Hint:
ls app/Events app/Listeners; grepevent(,::dispatch(.
- Hint:
- Helper vs facade idiom: global helpers (
config(),auth(),response()) vs facades (Config::,Auth::,Response::).- Hint: ratio of
config(vsConfig::(etc.) acrossapp/.
- Hint: ratio of
- Namespace layout (architecture): default
app/skeleton vs domain/module folders (app/Domain/**, modules).- Hint:
ls app/, look forDomain/,Modules/, bounded-context folders.
- Hint:
- Enums: backed vs pure; case naming; where they live.
- Hint:
ls app/Enums; grepenum .*: string,enum .*: int.
- Hint:
F. Frontend & views
No Livewire/Inertia/Flux package is installed. This app may be API-only. Confirm from resources/views before sweeping, and treat the Livewire/Flux dimensions as not applicable.
- Frontend stack: Blade+Livewire vs Inertia (Vue/React/Svelte) vs Blade-only / API + separate SPA.
- Hint:
composer.json+package.json;ls resources/js/pages,resources/views.
- Hint:
- Blade composition: class
<x-*>components vs anonymous components (@props) vs@includepartials.- Hint:
ls app/View/Components; grep<x-,@includeinresources/views.
- Hint:
- Localization: short keys (
lang/*/*.php+__('messages.welcome')) vs JSON string keys (lang/*.json+__('Full sentence')).- Hint:
ls lang; grep dotted__('vs sentence keys.
- Hint:
G. Database & migrations
- Foreign keys:
foreignId()->constrained()vsforeignIdFor(Model::class)vs manualforeign()->references()->on().- Hint: grep
foreignId(,foreignIdFor(,->foreign(indatabase/migrations.
- Hint: grep
down()methods: real reverse logic vs omitted / one-way migrations.- Hint: grep
function downvs the migration count.
- Hint: grep
- Enum storage: DB
enum()column vsstring()+ PHP-enum cast on the model.- Hint: grep
->enum(in migrations vs string columns cast to enums.
- Hint: grep
- Transactions:
DB::transaction(fn ...)closure vs manualbeginTransaction/commit/rollBack.- Hint: grep
DB::transaction,beginTransactioninapp/.
- Hint: grep
- Idempotent writes:
upsert/updateOrCreate/firstOrCreatevs find-then-save.- Hint: grep
upsert(,updateOrCreate(,firstOrCreate(inapp/.
- Hint: grep
H. Testing
- Framework: Pest (
it()/test()/expect()) vs PHPUnit classes.- Hint:
ls tests/Pest.php; grepit(/test(vsextends TestCase.
- Hint:
- DB reset:
RefreshDatabasevsDatabaseTruncationvsDatabaseMigrations.- Hint: grep those trait names in
tests/.
- Hint: grep those trait names in
- Fixtures: compare how equivalent test-owned records are created, such as factories vs manual inserts. Track seeders separately for shared reference data because
$this->seed()commonly and legitimately coexists with factories.- Hint: grep
::factory(and direct inserts intests/; separately inspect$this->seed(calls and what those seeders provide.
- Hint: grep
- Collaborator isolation: how the app doubles its own classes, Mockery
mock()/spy()vs real integration. Ignore facade fakes likeMail::fake()here, they isolate framework services by default and are not a fork against Mockery.- Hint: grep
->mock(,->spy(,Mockery::intests/.
- Hint: grep
- Endpoint assertions: array
assertJson([...])/assertJsonFragmentvs fluentAssertableJson.- Hint: grep
AssertableJson,assertJsonFragmentintests/.
- Hint: grep
I. Responses & API resources
- Response shape: API Resource classes vs
response()->json()vs returning models/arrays directly.- Hint:
ls app/Http/Resources; grepJsonResource,->json(in controllers.
- Hint:
- Resource relationship inclusion:
whenLoaded()guards vs unconditional relationship access. Do not count ordinary scalar attributes as rivals to conditional relationships, and evaluate generalwhen()fields separately.- Hint: compare relationship fields using
whenLoaded(with unconditional relationship property access inapp/Http/Resources.
- Hint: compare relationship fields using
- Pagination contracts: within comparable endpoint categories, length-aware
paginate()vssimplePaginate()vscursorPaginate(). These have different totals, navigation, ordering, and performance contracts, so record only a stable path-scoped API policy, never a project-wide majority.- Hint: grep those in
app/, then group matches by endpoint type and client contract before comparing them.
- Hint: grep those in
- Web redirects/URLs:
route('name')vsurl('/path')vsaction([...]).- Hint: grep
route(',url('/,action([inapp/Httpand views.
- Hint: grep
J. Strings, collections & dates
- Iteration idiom:
collect()->map()->filter()pipelines vsarray_map/foreach.- Hint: grep
collect(,->map(vsarray_map,foreachdensity inapp/.
- Hint: grep
- String API: fluent
Str::of()->...(Stringable) vs staticStr::vs native (trim,strtoupper).- Hint: grep
Str::of(vsStr::vs native string funcs.
- Hint: grep
- Dates: compare equivalent construction call styles (
now()/today()helpers vsCarbon::) separately from the application's mutable/immutable date policy.Date::use(CarbonImmutable::class)can make helpers return immutable dates, so those signals are complementary rather than conflicting.- Hint: grep
now(andCarbon::for call style; separately inspectCarbonImmutableandDate::usefor mutability policy.
- Hint: grep
Genuine forks only. Every row survived the "no tool can decide this, and it isn't the default" filter. Give each applicable dimension exactly one verdict: pattern, conflict, default, no-signal, tooling-owned, or already-recorded. The rows tagged (architecture) are where the highest-value rules come from.