Upgrading to Laravel 9: Modernizing Your PHP Workflow

Overview

marks a significant shift for the framework, transitioning to a yearly release cycle and embracing the latest
PHP
8.1 features. This update focuses on internal modernization, replacing aging dependencies like Swift Mailer with more robust
Symfony
components and enhancing developer quality of life through cleaner syntax and smarter defaults.

Prerequisites

To follow along, you should have a solid grasp of

and previous experience with
Laravel
8. You must have
PHP
8.0 or higher installed, as this version drops support for older
PHP
environments.

Key Libraries & Tools

  • Flysystem v3: Powers the Storage facade with a rewritten, modern architecture.
  • Symfony Mailer: Replaces the deprecated Swift Mailer for all email operations.
  • Guzzle: The underlying engine for
    Laravel
    's HTTP client.

Code Walkthrough

Simplified Route Groups

Laravel 9 introduces a more readable way to group routes that share a single controller. This eliminates the need to repeat the controller class name for every route definition.

use App\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;

Route::controller(OrderController::class)->group(function () {
    Route::get('/orders/{id}', 'show');
    Route::post('/orders', 'store');
});

Previously, you had to pass an array or a string for every single route. The controller() method keeps your routes/web.php file incredibly dry.

Enum Route Binding

With

8.1 support, you can now type-hint
PHP
directly in your route signatures.
Laravel
automatically performs the backing-value lookup or returns a 404 if the value is invalid.

enum Status: string {
    case Shipped = 'shipped';
    case Delivered = 'delivered';
}

Route::get('/orders/status/{status}', function (Status $status) {
    return $status->value;
});

Syntax Notes

  • Anonymous Migrations: By default, php artisan make:migration now uses return new class extends Migration. This prevents class name collisions when merging features from different branches.
  • HTTP Timeouts: The HTTP client now defaults to a 30-second timeout. This prevents hanging processes in your queue workers if a third-party API stops responding.

Practical Examples

  • Storage Operations: When using Storage::put(), be aware that Flysystem v3 now overwrites files by default. Always use Storage::exists() if you need to prevent data loss.
  • Queue Maintenance: Use the updated php artisan queue:flush --hours=24 to prune old failed jobs without wiping your entire history.

Tips & Gotchas

  • Mailer Migration: If you use withSwiftMessage, you must rename it to withSymfonyMessage to avoid crashes.
  • Validation Changes: Be careful when validating nested arrays.
    Laravel
    9 no longer returns unvalidated keys in the $request->validated() output, which is a breaking change from version 8.
3 min read