stickee/larastan-config
Opinionated Larastan/PHPStan configuration for Stickee Laravel projects, providing a shared static analysis baseline and sensible rules to keep code quality consistent across applications and packages.
stickee/larastan-config is a shared PHPStan/Larastan ruleset rather than a runtime library — its value is a pre-tuned phpstan.neon you include instead of hand-writing ignores for every Laravel facade, macro and magic property.
composer require --dev stickee/larastan-config
Then create (or edit) phpstan.neon.dist in your project root:
includes:
- vendor/stickee/larastan-config/phpstan.neon
parameters:
paths:
- app
- routes
- database
vendor/bin/phpstan analyse --memory-limit=1G
Where to look first: open vendor/stickee/larastan-config/ and read the .neon file(s) before you run anything. There are only a handful of things that matter in it: the level, the excludePaths, the ignore list, and any custom services (rules) it registers. That file is the package; everything else is metadata. Also check its composer.json for an extra.phpstan.includes block — if present, phpstan/extension-installer already auto-loads it and your manual includes: is redundant.
First use case: dropping static analysis onto an existing Laravel app that has never been analysed. Add the include, run at the vendor's default level, and let the ignore list absorb the framework noise. Expect a manageable first-run error count rather than thousands of facade/Model::__call false positives — that's the whole point of the package.
Layer your own config on top, never beside it. The include is a baseline; your file is the diff.
includes:
- vendor/stickee/larastan-config/phpstan.neon
- phpstan-baseline.neon
parameters:
level: 8 # override the vendor level upward
paths:
- app
- tests
excludePaths:
analyse:
- app/Console/Kernel.php
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
editorUrlTitle: '%%file%%:%%line%%'
Ratchet the level instead of adopting it wholesale. Run --level=0 against the vendor include, fix what's cheap, then raise. Because the vendor config already handles framework magic, each level bump is mostly your code, which makes the work estimable.
Generate a baseline for the legacy surface, not for the framework.
vendor/bin/phpstan analyse --generate-baseline
Commit phpstan-baseline.neon, review the diff on every PR, and treat growth in the baseline as a review failure. Baselines only record current errors, so any false positive the vendor config doesn't cover lands in the baseline — check periodically whether an ignore in your config is now dead (reportUnmatchedIgnoredErrors will tell you).
Separate app and test analysis. A single config that includes tests/ makes PHPStan chew through fixtures and mocks. Two config files, one CI job each, keeps runtime sane:
# phpstan-tests.neon
includes:
- phpstan.neon
parameters:
paths:
- tests
CI wiring. Cache the result cache and pin the PHPStan major:
- run: composer install --no-interaction --prefer-dist
- uses: actions/cache@v4
with:
path: /tmp/phpstan
key: phpstan-${{ hashFiles('composer.lock', 'phpstan*.neon') }}
- run: vendor/bin/phpstan analyse --configuration=phpstan.neon --no-progress --error-format=github
--error-format=github turns findings into inline PR annotations — this is what actually gets developers to fix them.
Fork it for anything long-lived. With zero dependents and zero stars, treat the upstream as a snapshot, not a dependency you track. Fork into your org, add your own ignores there, and consume it via a VCS repository. You get the curated Laravel ignores plus a place to put the rules you'd otherwise copy-paste into fifteen projects.
NEON merge semantics will surprise you. Associative keys merge deeply, but list keys append — you cannot remove an entry the vendor config already added to excludePaths.analyse or ignoreErrors. To exclude a path the vendor includes, or to un-ignore an error, you have to override the entire key. Read the vendor file and know what you're inheriting.
Include paths are relative to the file that declares them, not to your CWD. A vendor config that includes phpstan-baseline.neon is pointing at its own directory; if you get File ... not found, you've misread which file owns the include.
Version coupling is the real risk. Shared configs reference parameters that move between PHPStan majors (checkMissingIterableValueType, checkGenericClassInNonGenericObjectType, treatPhpDocTypesAsCertain and friends have been added, deprecated and removed across 1.x and 2.x). If the vendor config sets a parameter your installed PHPStan no longer knows, boot fails with Unexpected item 'parameters › ...' — not a nice warning. Pin phpstan/phpstan and larastan/larastan in composer.json and bump them deliberately, together, with the config.
A registered custom rule that can't load kills the whole run. If the package's services: section points at a rule class whose own dependencies aren't installed in your project, PHPStan won't degrade gracefully. Check services in the neon file against your composer.lock if you see a container/DI error before analysis starts.
Larastan must be present and bootstrapped. These configs assume larastan/larastan is installed and, for full accuracy, that Laravel's container can boot — a broken bootstrap/app.php, a missing .env, or a service provider that touches the database will surface as analysis failures. Use --debug to see the real exception behind a vague internal error.
Debugging the config itself:
vendor/bin/phpstan analyse -vvv # which config files were loaded
vendor/bin/phpstan diagnose # environment/extension sanity check
-vvv is the fastest way to confirm whether the vendor config is actually in the graph or silently ignored.
Clear the result cache when the ruleset changes. PHPStan keys its cache partly on config, but stale results after a dependency bump are common enough that rm -rf /tmp/phpstan belongs in your troubleshooting notes.
Don't fight an ignore you disagree with — fork. If the vendor ignores something you consider a genuine bug class (missing iterable value types, for instance), overriding it project-by-project recreates the copy-paste problem the package exists to solve.
How can I help you explore Laravel packages today?