Back to blog

Leaving PHP, Part 8: the tooling exists, but it is not the default experience

Part 1 looked at hiring. Part 2 looked at the runtime. Part 3 looked at types. Part 4 looked at the ecosystem. Part 5 looked at the type seam. Part 6 looked at concurrency. Part 7 looked at deployment. Part 8 is about the daily loop: the tools you touch every hour of every workday. PHP has excellent tools. It does not have an excellent default toolchain.

1
Commands to a wired dev environment in the JS, Rust, and Go ecosystems: npm create vite, cargo new, go mod init. TypeScript, formatting, linting, tests, and a build ship with it.
5+
Separate packages a PHP dev typically installs to reach parity: PHPStan or Psalm, PHP-CS-Fixer or Rector, Pest or PHPUnit, a debug bar, a dev-server story. Each is excellent. None of them are the default.
0
Officially blessed PHP toolchain that just runs after install. The ecosystem has consensus; the language has none.

What the default experience looks like elsewhere

Run npm create vite@latest in an empty directory and you are ninety seconds from a running application. TypeScript is on by default. A dev server with hot module replacement is running. A production build target is wired. Prettier and ESLint have configs. Testing is a one-liner away, with Vitest already understanding the same module graph the app uses. The entire loop, from a blank folder to a code change appearing in the browser, is a single command and no reading.

Rust does the same with cargo. One binary handles dependency resolution, builds, tests, benchmarks, docs, and formatting through cargo fmt. Go does the same with a smaller surface area: go mod, go build, go test, go fmt, all shipped with the language itself, no configuration to think about. Each ecosystem has settled on one obvious default, and the default is genuinely opinionated. The value of that opinion is that you do not have to form one of your own on day one.

PHP's tools are excellent, in isolation

None of this is a shortage of quality in the PHP ecosystem. PHPStan is one of the best static analysers in any language. Psalm is right there beside it. PHP-CS-Fixer is a mature, extremely configurable formatter. Pest is a genuine pleasure to write tests in. PHPUnit is the industry-standard reference test runner for the whole language ecosystem. Rector rewrites your code across major PHP versions with a precision most languages do not have.

Take any one of these in isolation and it holds up against its counterpart in any other language. The individual quality is not the problem. What is missing is a layer above them: an opinionated default that says pest is the runner, phpstan is the analyser at level 6, PHP-CS-Fixer runs on save with the PSR-12 profile, and you get all of that by typing one command in a fresh project.

Assembly is the daily tax

The gap between "the tools are excellent" and "the toolchain is excellent" is where the friction lives. A new Laravel project ships with Pest configured, which is a real improvement. It does not ship with a preconfigured PHPStan level, or a linter running in CI, or a formatter on commit, or a specific IDE debugging profile, or a common set of Rector rules for cross-version safety. Every team assembles those for themselves. Every senior developer has strong opinions on which combination they want. Every new hire has a slightly different opinion, or none.

The assembly is fine when the person doing it knows what they are doing. It becomes a daily tax when the same conversation has to be had on every new repository, every new team, and every new employee onboarding. Which analyser do we use? Which level? Which fixer rules? Do we run the fixer in CI or on commit? Do we run PHPStan in CI or leave it as a nudge? None of these are difficult questions. All of them are questions that other ecosystems have answered on the user's behalf.

Where the assembly hurts most

For a senior PHP engineer with an existing codebase and a wired-up team, none of this is a live problem. The tools are installed, the CI runs them, the muscle memory is there, and the loop works. The cost, when there is one, is on the margin: a new microservice that has to set the whole thing up again, an experimental repo where nobody bothered because the setup cost outweighed the payoff.

Where it hurts more is onboarding: a new hire spends two days getting their environment to match the rest of the team, reading which tool is used where, remembering which command runs which subset. It hurts more on greenfield: a new project starts with a discussion about the toolchain instead of a discussion about the product. It hurts most on focus: any hour spent tuning tools is an hour not spent on the thing you are actually trying to build. The tax is small per interaction and constant over time.

A scorecard for the tooling surface

No opinionated default toolchain
No PHP equivalent of cargo, go fmt, or npm create. Composer manages dependencies; it does not opinionate the rest.
Assembly is per-project, per-team, per-hire
Every codebase settles on its own combination. Every new engineer relearns the local choices.
=
The individual tools are excellent
PHPStan, Psalm, PHP-CS-Fixer, Pest, PHPUnit, Rector. Best-in-class components without a best-in-class default composition.
Framework starters cover some of it, not all
Laravel ships Pest, which helps. It does not ship a wired PHPStan level, a formatter on commit, or an opinionated linter policy.
The onboarding cost compounds
Small per interaction, constant over time. A hidden line item on every project.

None of this is a dealbreaker in isolation. Every previous post in this series has ended with the same observation and it applies here too. The individual arguments are small; the aggregate is the whole point. Tooling is one more corner where the alternative ecosystems have paved a road and PHP has not.

Up next in the series

Two posts to go. Part 9 makes the case that incremental rewriting has become realistic in a way it was not five years ago, and lays out the practical shape of a strangler-fig migration. Part 10 steps back to the strategic frame: for a decade-scale platform bet, what actually compounds, and what does not.

If you want to see the toolchain Pext ships, or discuss what it would take to lift your codebase off the current one, book a demo.