Labrodev static analysis
Skill labrodev/laravel-playbook/skills/labrodev-static-analysis
Use when configuring, running, or fixing findings from the code-quality toolchain in a Labrodev Laravel project — Pint (pint.json, code style), PHPStan/Larastan (phpstan.neon, levels, baselines, generics errors), or Rector (rector.php, automated refactoring) — or when deciding whether a tool error may be suppressed.From its SKILL.md
npx -y skills add labrodev/laravel-playbook --skill labrodev-static-analysisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.7 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it
Static Analysis: Pint, PHPStan, Rector
Part of the Labrodev playbook skill set — assumes labrodev-core and labrodev-naming are installed. If absent, minimum global rules: declare(strict_types=1), final classes, App/Layer depends on Core (never the reverse), named-argument invocation.
These tools are not optional. They are part of the architecture: they reduce review noise, prevent trivial mistakes, and enforce consistency automatically so humans review architecture and behavior instead of formatting and types. If a change requires fighting the tools, reconsider the design — not the tool.
Musts
- All three tools run on the modified code after the work is done, before commit/PR — and all three must pass.
- Run in this order: Rector → Pint → PHPStan. Rector rewrites code (needs a style pass afterwards); PHPStan verifies the final result.
- Tool configuration files (
pint.json,phpstan.neon,rector.php) are canonical and live in the repo root. Changing them is an architectural decision, not a convenience fix. - PHPStan runs with Larastan and must pass at the configured level for all modified code. Type safety beats convenience.
- The ide-helper mixin (
_ide_helper_models.php) must be current before analysing — it carries model column metadata → see the labrodev-model skill.
Must-nots
- Never hand-fix what Pint fixes — run the tool.
- Never suppress a PHPStan error: no
@phpstan-ignore, no baseline file, no level drop. Fix the root cause; if that seems impossible, the design is wrong — reconsider it. - Never ignore a Rector suggestion without a reason. If a Rector rule fights the playbook, exclude the rule in
rector.phpexplicitly — do not skip runs. - Never delete a "dead" null-guard in
creating()/saving()hooks just because PHPStan flags it — usegetAttribute()instead (see the recurring trap below). - Never run tools only on the happy path: exports, jobs, observers, and casters are modified code too.
Commands
vendor/bin/rector process app/ src/ # automated refactoring (review the diff!)
vendor/bin/pint # code style — config is canonical
vendor/bin/phpstan analyse --memory-limit=2G # static analysis (Larastan) — must pass
When the local PHP version differs from the project's required version, run the tools through the project runtime (Sail): vendor/bin/sail bin phpstan analyse --memory-limit=2G — never against a mismatched local PHP.
Composer scripts (recommended, so every agent and CI runs the same thing):
"scripts": {
"refactor": "rector process",
"lint": "pint",
"analyse": "phpstan analyse --memory-limit=1G",
"check": [
"@refactor",
"@lint",
"@analyse"
]
}
Pint
Pint enforces formatting, imports, spacing, and modern PHP conventions. The laravel preset is the base; add the rules that mechanically enforce the playbook's file-header contract instead of relying on discipline:
{
"preset": "laravel",
"notPath": [
"_ide_helper_models.php"
],
"rules": {
"declare_strict_types": true,
"final_class": true,
"fully_qualified_strict_types": true,
"ordered_imports": {
"sort_algorithm": "alpha"
}
}
}
The generated ide-helper mixin file stays committed (CI needs it for PHPStan) and is excluded from Pint via notPath.
declare_strict_typesandfinal_classturn two core Musts into machine-enforced facts (see the labrodev-core skill for the rules themselves).final_classmust not touch abstract base classes (it skips abstract classes by design) —BaseModelstays abstract and unfinalized.- Run on all modified files before every PR; Pint's output is never "reformatted back".
PHPStan (Larastan)
includes:
- vendor/larastan/larastan/extension.neon
- vendor/nesbot/carbon/extension.neon
parameters:
level: 6
paths:
- app
- routes
- tests
- src
scanFiles:
- _ide_helper_models.php
- Level 6 is the default; the level may vary per project — check
phpstan.neon— but never lower an existing project's level to make an error disappear. - Root-cause fixes only: no baseline file, no
@phpstan-ignoreanywhere. An error is fixed at its source or the design is reconsidered — never annotated away. - The Carbon extension (
nesbot/carbon) closes date-handling false positives; include it alongside Larastan. scanFilespulls in the ide-helper mixin so PHPStan knows every model column without@propertylists in the models → see the labrodev-model skill. Regenerate the mixin after any schema change, BEFORE analysing.- Larastan resolves relation and cast types; the playbook's generics docblocks close the rest:
@return Builder<Model>on Query methods (labrodev-query),@extends QueryBuilder<Model>on IndexQueries (labrodev-query),@extends Collection<int, Model>on Collections (labrodev-model),@return BelongsTo<User, $this>on relation methods (labrodev-model). - Dead code policy: when an error traces back to genuinely unused code (orphaned tests for never-created classes, zero-call-site services, dead configs), DELETE the code — never annotate around it or leave a stub. If we never use it anywhere — just remove.
Recurring trap: pre-save null guards vs. docblock non-null
A generated @property string $uuid (NOT NULL column) makes PHPStan flag === null checks inside creating()/saving() hooks as dead code — but before the first save, the in-memory model genuinely has no value there. The docblock describes a persisted row, not a pre-save instance.
- Wrong fix: deleting the null-guard because PHPStan calls it dead — this has broken real test suites (NOT NULL violations on insert).
- Right fix: read via
$model->getAttribute('uuid')inside creating/saving hooks — it returnsmixed, so the guard stays live AND PHPStan-clean. - Related nuance when "simplifying":
$a->b ?? $cis null-safe only for the left arm (??uses isset semantics) — the right arm still needs?->if it can be null. Never strip both operators at once.
Rector
Rector keeps the codebase modern and consistent: PHP-version upgrades, dead-code removal, and Laravel-specific refactors (via driftingly/rector-laravel). Installing the package without a rector.php means Rector never runs — configuration is part of adoption, not a later step.
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Php85\Rector\LevelSetList;
use RectorLaravel\Set\LaravelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__.'/app',
__DIR__.'/src',
])
->withPhpSets()
->withSets([
LaravelSetList::LARAVEL_CODE_QUALITY,
])
->withImportNames(removeUnusedImports: true);
- Always review Rector's diff before committing — it is a refactoring tool, not a formatter.
- Rector output goes through Pint before PHPStan (the run order above).
- A rule that conflicts with a playbook convention gets excluded explicitly in
rector.phpwith the reason in the PR that excludes it.
Relationship to architecture tests
Pint/PHPStan/Rector enforce style, types, and syntax. Playbook structure (no FormRequests, Core never imports App\Layer, controllers stay thin) is enforced by Pest architecture tests → see the labrodev-testing skill. Both gates run before every PR; neither substitutes for the other.
Review checklist
- Did Rector, then Pint, then PHPStan run on all modified code — and do all three pass?
- Is the PHPStan level unchanged (or raised), with no baseline file and zero
@phpstan-ignoreannotations anywhere? - Did errors get root-cause fixes — and did errors tracing to genuinely unused code result in deletion, not annotation?
- Was the ide-helper mixin regenerated after any schema change, before the PHPStan run (and does it stay committed, Pint-excluded via
notPath)? - Do
pint.jsonrules still enforcedeclare_strict_typesandfinal_class? - Does
rector.phpexist and cover bothapp/andsrc/(an installed-but-unconfigured Rector runs nothing)? - Were tool configuration changes made deliberately and reviewed as architecture, not slipped in to silence an error?
- Are pre-save null-guards implemented via
getAttribute()rather than deleted when PHPStan flags them as dead?
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.