A reader wrote to me last week with a screenshot of her CI run: PHP-CS-Fixer, PHP_CodeSniffer, PHPStan at level 8, Deptrac, all lined up one after another, eleven minutes before anyone learned whether a typo in a docblock had broken the build. Her question was short. Should she rip all of it out and put Mago in its place? My answer is no, not all of it, and not this quarter. What she should do tomorrow morning is put Mago in her pre-commit hook and let it own formatting and linting outright.
Quick context if you haven't run into it. Mago is a single static binary written in Rust, from Carthage Software, that bundles a formatter (mago fmt, PER-CS by default, with psr-12, laravel and drupal presets), a linter (mago lint, 190 rules across 9 categories as of 1.51.2), a static analyzer (mago analyze, which reads PHPStan and Psalm annotations including generics and conditional types) and an architectural guard (mago guard, covering ground that Deptrac and PHPArkitect cover today). Everything sits in one mago.toml, it's been on stable 1.x since December 2025, and it's licensed MIT or Apache 2.0. You can pull it in with composer require --dev carthage-software/mago, or skip the PHP runtime entirely and use the install script.
The number everyone repeats is from the project's own benchmarks: linting roughly 29 times faster than PHP-CS-Fixer, thanks to native code and a pipeline spread across every core. Vendor benchmarks deserve a raised eyebrow, sure, but even if your codebase only sees a third of that, the change is bigger than it sounds. A check that takes forty seconds lives in CI and gets ignored until the pipeline goes red. A check that takes under two seconds can run on every save, and suddenly the formatting argument in code review just stops happening because the diff never contained it. Speed decides where a check gets to live, and that's where Mago earns its place first.
Now the strongest case for going all in, because it's a good one. Seven tools means seven configs drifting apart, seven parsers that disagree about edge cases in new syntax, and a composer.json full of dev dependencies that fight each other on every PHP minor upgrade. One parser shared by every check is genuinely cleaner engineering. And the guard is quietly excellent: being able to say in config that everything under App\Controller must be final and named *Controller, or that App\Domain may only depend on itself and native PHP, without a separate tool and a separate YAML dialect, is the sort of thing teams talk about for years and never set up. If you've never enforced layering because Deptrac felt like one more thing to babysit, Mago lowers that bar a lot.
Here's why I still keep PHPStan in the pipeline for now. The value of my analysis setup doesn't come from the core engine alone; it comes from the Symfony extension that knows what the container returns, the Doctrine extension that understands my repositories, and three years of baseline I've negotiated down entry by entry. Mago's own maintainers credit PHPStan and Psalm as inspiration and don't claim to match that plugin ecosystem yet. Swap the analyzer before checking that coverage and you'll get one of two outcomes: a flood of false positives that trains your team to ignore the tool, or silence where there used to be a real warning. Neither one shows up in a benchmark chart.
So my split is boring and I'm fine with that. Formatter and linter: hand them to Mago, run mago format --check and mago lint in the hook and in CI, use a baseline for the legacy corners, and lean on mago lint --explain when someone asks why a rule fired. Guard: adopt it if you have nothing, migrate off Deptrac at leisure if you do. Analyzer: run mago analyze next to your existing tool for a few sprints, diff what each one reports, and only retire the old one once the gap is something you can name and accept. One practical tip while you're in mago.toml: write php-version = "8.3" with quotes, because TOML reads the bare 8.3 as a float and Mago wants a string. That one will cost you ten confused minutes otherwise.
There's a softer question under all this that I keep chewing on. Our quality tooling has always been written in PHP, which means any of us could open the source of a sniff or a PHPStan rule, understand it, and send a fix on a Friday afternoon. Mago moves the engine to Rust, and the Go crowd will tell you that's simply how fast tooling gets built these days. Fair enough. PHP got its own tooling this far by being something its users could hack on, though, and I'd like to know whether that survives when the rule you want to tweak lives in a crate.
So here's what I'd really like to hear from you in the comments: if you've already run mago analyze against a real Symfony or Laravel codebase alongside PHPStan, what did it catch that PHPStan missed, what did it miss that PHPStan caught, and was the difference small enough for you to switch?




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.