imanghafoori/laravel-self-test
A self-testing toolkit for Laravel apps: write fluent health checks that verify routes, views, configs, and service providers, catching misconfigurations and broken references before they reach production. Run them via artisan or in your test suite.
Install it as a dev-only dependency — this is a "self-test" tool that inspects your app's source, not something that ships to production:
composer require --dev imanghafoori/laravel-self-test
Then run the aggregate command to see what it finds across the whole app:
php artisan microscope
microscope fans out to a set of focused check:* commands. Discover the full set with:
php artisan list | grep check
Where to look first: start with the three checks that catch errors ordinary static analysis (PHPStan/Larastan) misses because they depend on Laravel conventions rather than types:
php artisan check:views # view() names that don't resolve to a Blade file
php artisan check:routes # controller actions referenced by routes that don't exist / route conflicts
php artisan check:imports # unused imports left behind by a refactor
First real use case: you've just renamed or moved a controller/blade file. Run check:views and check:routes before committing. These are string-based references (view('admin.users.index'), 'UserController@show') that neither your IDE nor PHP itself will flag — but they blow up at runtime. That's roughly 80% of the day-to-day value.
Treat microscope like a linter: useful every day, but it's a pre-commit / CI tool, not something you run once and forget.
First run php artisan microscope locally on a clean branch and triage the output. Some checks are stylistic and opinionated (e.g. early returns, negated else, action docblocks); decide which ones you actually want as house rules and exclude the rest. Then wire the survivors into CI:
# .github/workflows/ci.yml
- run: composer install --no-interaction
- run: composer dump-autoload -o # PSR-4 checks depend on this
- run: php artisan check:views
- run: php artisan check:routes
- run: php artisan check:imports
Most check:* commands exit non-zero when they find something, so they slot into CI as-is. Verify exit behaviour once per command (php artisan check:views; echo $?) before relying on it — a few commands are interactive and won't fail a build on their own.
Full-repo scans get slow on large codebases. Target only what you touched:
git diff --cached --name-only --diff-filter=ACM \
| grep '\.php$' \
| xargs -I{} php artisan check:imports --file={}
Many commands accept --file= / --folder= to scope the scan; confirm with php artisan help check:imports. This turns Microscope into a fast, actionable pre-commit hook instead of a "run it on Friday" chore.
The package's tagline — "fearless refactoring" — is about catching the string references to classes that ::class refactors and IDE renames miss:
# after renaming a class / moving namespaces
composer dump-autoload
php artisan check:psr4 # namespace vs. file path mismatches
php artisan check:stringy_classes # string class refs that should be ::class
php artisan check:classy # the inverse — unnecessary string usage
check:stringy_classes is what you run before a big rename to find dynamic app('App\Services\Foo') / resolve('...') call sites that will silently break. check:psr4 is what you run after moving files between directories.
After frontend/route reshuffling:
php artisan check:views # missing blade files
php artisan check:extract_blades # inline view strings that should be extracted
php artisan check:routes # dead controller actions, route conflicts
php artisan check:dead_controllers # controllers no longer referenced anywhere
check:dead_controllers is a nice pruning pass before a release — pair it with a git grep spot-check before deleting.
Think of the pipeline as three layers, each catching different classes of bug:
Add them to a single composer script so nobody has to remember the order:
"scripts": {
"test": [
"@php artisan check:imports",
"@php artisan check:views",
"@php artisan check:routes",
"phpunit"
]
}
view($name), app($class), class names built from concatenation, route actions assembled at runtime — are invisible to it. Expect both false positives (it flags something valid) and false negatives (it misses something broken). It's a complement to PHPStan, not a replacement.git status so the resulting diff is reviewable — never on top of uncommitted work you can't diff.--no-interaction (and skip fix flags) in pipelines; otherwise a CI job will hang waiting for a y/n.php artisan route:clear && php artisan config:clear before scanning. Route checks can't see routes registered conditionally on environment.composer dump-autoload first, or you'll chase phantom namespace mismatches.microscope on a 5,000-file app is slow. Split into per-directory jobs (--folder=app/Domain/Billing) or run only the changed-file loop in pre-commit and the full sweep nightly.require-dev and exclude it from production images. It has no runtime role and shouldn't be in your deploy artifact.microscope run means "no known Laravel-convention smells," not "this refactor is correct." Pair it with an actual test suite — it's a smoke detector, not a sprinkler system.vendor/imanghafoori/laravel-self-test if you need to know exactly what's configurable in your pinned version.How can I help you explore Laravel packages today?