imanghafoori/laravel-microscope
Laravel Microscope is a static analysis tool that scans your Laravel codebase without running it, catching bugs, code smells, and convention issues across routes, views, classes, and more. It reports problems and suggests fixes so you can clean up your project fast.
Install (dev only):
composer require imanghafoori/laravel-microscope --dev
The service provider is auto-discovered; there is no required configuration to get value on day one. On Laravel 5.5+ (through current versions) the package registers its Artisan commands automatically.
First command to run:
php artisan check:all
This runs the full suite of Laravel-aware static checks against your application and prints one block of findings per checker. To see the shape of the output without waiting on everything, start with a single checker:
php artisan check:imports
Where to look first:
php artisan check:imports — unused/incorrect use statements. This is the highest signal-to-noise check and the fastest win on any codebase.php artisan check:imports --fix — auto-removes dead imports. Run it on a clean git tree, then git diff to review.php artisan check:all — once imports are clean, this gives you a realistic picture of the rest of the backlog (routes, views, translations, dead controllers).First use case: drop check:imports --fix into your workflow as a cleanup pass on the directories you already own (app/, routes/, database/). It is safe, mechanical, and immediately reduces diff noise in later PRs. Only after that should you turn on the broader checks, because they surface findings that need judgment.
Triage-as-you-touch-a-feature. Microscope is most useful when scoped to the area of a PR rather than as a monolithic gate:
| You changed… | Run |
|---|---|
| Controllers / services | check:imports, check:classes |
routes/web.php |
check:routes, check:dead_controllers |
| Blade templates | check:views, check:extract_blades |
Lang files / __() calls |
check:strings |
| Middleware / policies | check:middlewares, check:gates |
CI as a second line of defence. Add a job after your test suite so a broken reference is caught even if no test covers that path:
- run: php artisan check:all
Because the checks are static, they catch dead route(), view(), and translation references that only fail at runtime on an untested branch. Order it after PHPStan/Pint so the cheap, deterministic tools fail first and Microscope runs on code that already parses cleanly.
Composer script for local parity with CI:
"scripts": {
"check": ["@php artisan check:all"],
"fix": ["@php artisan check:imports --fix"]
}
Then composer fix before committing and composer check before pushing — same commands locally and in CI, so there is no divergence.
Legacy-codebase rollout. Do not enable check:all on a ten-year-old app and expect a usable signal — you will get hundreds of findings and the team will mute the tool. Instead:
check:imports --fix once across the whole app and commit it as a standalone, reviewable "chore" commit.check:all to CI with the legacy directories excluded in config/microscope.php (publish it with vendor:publish).Dead-controller pruning. check:dead_controllers finds controllers no route points at. This is a reliable, low-risk way to identify removable code — verify the controller isn't resolved from the container by a string name before deleting, then pair the deletion with a route file cleanup.
Blade extraction suggestions. check:extract_blades flags duplicated markup across templates and suggests extracting a partial. Treat the output as a code-review prompt, not an autofix — the suggested partial boundary is not always the right abstraction.
Pair with, don't replace, PHPStan. Microscope understands Laravel conventions (view(), route(), __(), container strings) that PHPStan misses without Larastan stubs; PHPStan understands types and generics that Microscope does not. Run both.
--fix writes to disk. Always commit or stash first. Review the diff — auto-removal of an import can be wrong when the symbol is used only inside a string-evaluated context or a Blade template that the checker doesn't parse.view($template), route("{$prefix}.show"), and __("errors.{$code}") cannot be statically resolved. Expect these to be flagged; do not "fix" them by inlining literals. Exclude the file or ignore the finding.loadViewsFrom/loadTranslationsFrom are invisible to the scanner in some configurations. If a genuinely working reference is flagged, that is the usual cause.app/, check:all can take tens of seconds. Run individual checkers locally while iterating and reserve check:all for CI.config/microscope.php before customising exclusions — editing a non-published config silently does nothing. Remember to re-publish after major package upgrades; the option set changes between releases.if or a missing authorization check. Budget it as a linter, not as coverage.composer.json and bump deliberately. Checker sets and output formatting shift between releases, which can turn a passing CI run red on an unrelated PR.How can I help you explore Laravel packages today?