Laravel 10: Stricter Types and the Process Layer
Laravel 10's type discipline and the new Process facade: how the framework's maturing codebase translates into better day-to-day development.
Laravel 10 shipped in February 2023. Despite being a major release, the breaking changes list is remarkably short — a sign of the framework’s maturity. From the outside it might look like “nothing big happened.” But judging a release purely by new features is the wrong metric. The real value of Laravel 10 lies in its systematic move toward type consistency.
Framework code is now covered with type declarations
The entire Laravel source is now annotated with parameter and return type declarations. This is primarily an internal consistency win for the framework itself, but it has a direct and meaningful side effect on IDE support. When you navigate to a facade or contract method in PhpStorm or VS Code, you can now read from the signature exactly what it returns and what it expects.
The practical impact: even if you skip types in your own code, static analysis tools like Psalm and PHPStan produce realistic output at the boundary where your code meets the framework.
// Before Laravel 10: return type was ambiguous
public function find($id)
// With Laravel 10: readable from the signature
public function find(int|string $id): Model|null
This change doesn’t show up directly in your project, but it makes a difference: your IDE is no longer in the dark at the framework boundary.
I felt the concrete impact of this in my own projects when I raised the PHPStan level. Some checks that passed at level 5 under Laravel 9 started working significantly better after upgrading to 10, now that they could lean on framework types. Things that used to resolve as “unknown” are now wired to real types.
The new Process layer
The most tangible, practical addition in Laravel 10 is the Process facade. If you’ve ever used exec, shell_exec, or proc_open to spawn external processes in PHP, you know how raw those tools are: output handling, error capture, timeouts — everything had to be hand-rolled.
The Process layer wraps all of that:
use Illuminate\Support\Facades\Process;
$result = Process::run('git log --oneline -5');
if ($result->successful()) {
echo $result->output();
} else {
echo $result->errorOutput();
}
Setting a timeout, specifying a working directory, and passing environment variables are all done through fluent chaining:
$result = Process::timeout(30)
->path(base_path())
->env(['NODE_ENV' => 'production'])
->run('npm run build');
But the real value of the Process layer is on the testing side. With Process::fake() you can write tests without actually running system processes:
Process::fake([
'git *' => Process::result(output: 'abc1234 latest commit'),
]);
// Tests now run without sending a git command to the system
I’m evaluating this feature against a genuine need: getting processes that depend on external commands under test was already painful. Without a fake mechanism, you either run the real thing or mock the entire function. Neither is satisfying.
Skeleton and starting point
It’s worth noting that the laravel/laravel skeleton also received a subtle polish with Laravel 10. Type declarations became standard in routes/ files in particular. If you think that difference is small when starting a new project — give it a few months, and what that discipline means for the codebase becomes clear.
Especially on larger teams or long-running projects, a strict skeleton makes it easier for newly written code to hold the same line. It’s not mandatory, but breaking from a standardised approach requires a conscious decision; that friction is actually a safeguard.
Cost of upgrading
Migrating from Laravel 9 to 10 is one of the least painful major-version upgrades the framework has had. The official upgrade guide is short; for most applications, updating composer.json and making a handful of small fixes is all it takes. PHP 8.1 is the minimum requirement, and it’s fully compatible with PHP 8.2.
Checking third-party package compatibility with Laravel 10 before upgrading can sometimes be a pressure point. Looking at composer outdated output and reviewing version constraints makes upgrade planning easier. Most popular packages completed the transition quickly, but occasionally a package waiting for an update can push the migration back by a few weeks.
The message I take from this release: Laravel is making deliberate moves to make an already working framework more rigorous and predictable. You don’t have to expect a major feature in every major release; sometimes “the framework getting its own house in order” is the most valuable thing.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.