A look at what is coming to Laravel 9

News

January 6th, 2022

A look at what is coming to Laravel 9

Laravel v9 is the next major version of Laravel, and it was released February 8th, 2022. In this post, we wanted to outline all the new features and changes in this version.

Laravel 9 Release Date Changes

Laravel v9 was scheduled to be released around September of this year, but the Laravel Team decided to push this release back to February of 2022:

Laravel uses a variety of community-driven packages as well as nine Symfony components for a number of features within the framework. Symfony 6.0 is due for release in November. For that reason, we are choosing to delay the Laravel 9.0 release until 2022.
By delaying the release, we can upgrade our underlying Symfony components to Symfony 6.0 without being forced to wait until September 2022 to perform this upgrade. In addition, this better positions us for future releases as our yearly releases will always take place two months after Symfony's releases.

This will also push future major releases back as well, and here is the schedule going forward:

  • Laravel 9: February 8th, 2022
  • Laravel 10: February 7th, 2023

PHP 8 the minimum version in Laravel 9

Since Laravel 9 will require Symfony 6.0 and it has a minimum requirement of PHP 8 that means Laravel 9 will carry this same restriction.

New Design for routes:list

The routes:list command has been included in Laravel for a long time now, and one issue that some times arise is if you have a huge and complex routes defined it can get messy trying to view them in the console. Thanks to a pull request from Nuno Maduro this is getting a makeover.

New Test Coverage Option

A new artisan test --coverage option will display the test coverage directly on the terminal. It also includes a --min option that you can use to indicate the minimum threshold enforcement for test coverage.

Anonymous Stub Migrations

Earlier this year, Laravel 8.37 came out with a new feature called Anonymous Migrations that prevents migration class name collisions.

1use Illuminate\Database\Migrations\Migration;
2use Illuminate\Database\Schema\Blueprint;
3use Illuminate\Support\Facades\Schema;
4 
5return new class extends Migration {
6 
7 /**
8 * Run the migrations.
9 *
10 * @return void
11 */
12 public function up()
13 {
14 Schema::table('people', function (Blueprint $table) {
15 $table->string('first_name')->nullable();
16 });
17 }
18};

When Laravel 9 launches, this will be the default when you run php artisan make:migration

New Query Builder Interface

Thanks to Chris Morrell, Laravel 9 will feature a new Query Builder Interface, and you can see this merged PR for all the details.

For developers who rely on type hints for static analysis, refactoring, or code completion in their IDE, the lack of a shared interface or inheritance between Query\Builder, Eloquent\Builder and Eloquent\Relation can be pretty tricky:

1return Model::query()
2 ->whereNotExists(function($query) {
3 // $query is a Query\Builder
4 })
5 ->whereHas('relation', function($query) {
6 // $query is an Eloquent\Builder
7 })
8 ->with('relation', function($query) {
9 // $query is an Eloquent\Relation
10 });

This feature adds a new Illuminate\Contracts\Database\QueryBuilder interface and a Illuminate\Database\Eloquent\Concerns\DecoratesQueryBuilder trait that implements the interface in place of the existing __call implementation.

PHP 8 String Functions

Since PHP 8 will be the minimum, Tom Schlick submitted a PR to move to using str_contains(), str_starts_with() and str_ends_with() functions internally in the \Illuminate\Support\Str class.

From SwiftMailer to Symfony Mailer

Symfony Mailer support was contributed by Dries Vints, James Brooks, and Julius Kiekbusch.

Previous releases of Laravel utilized the Swift Mailer library to send outgoing email. However, that library is no longer maintained and has been succeeded by Symfony Mailer.

Please review the upgrade guide to learn more about ensuring your application is compatible with Symfony Mailer.

Flysystem 3.x

Flysystem 3.x support was contributed by Dries Vints.

Laravel 9.x upgrades our upstream Flysystem dependency to Flysystem 3.x. Flysystem powers all of filesystem interactions offered by the Storage facade.

Improved Eloquent Accessors / Mutators

Improved Eloquent accessors / mutators was contributed by Taylor Otwell.

Laravel 9.x offers a new way to define Eloquent accessors and mutators. In previous releases of Laravel, the only way to define accessors and mutators was by defining prefixed methods on your model like so:

1public function getNameAttribute($value)
2{
3 return strtoupper($value);
4}
5 
6public function setNameAttribute($value)
7{
8 $this->attributes['name'] = $value;
9}

However, in Laravel 9.x you may define an accessor and mutator using a single, non-prefixed method by type-hinting a return type of Illuminate\Database\Eloquent\Casts\Attribute:

1use Illuminate\Database\Eloquent\Casts\Attribute;
2 
3public function name(): Attribute
4{
5 return new Attribute(
6 get: fn ($value) => strtoupper($value),
7 set: fn ($value) => $value,
8 );
9}

In addition, this new approach to defining accessors will cache object values that are returned by the attribute, just like custom cast classes:

1use App\Support\Address;
2use Illuminate\Database\Eloquent\Casts\Attribute;
3 
4public function address(): Attribute
5{
6 return new Attribute(
7 get: fn ($value, $attributes) => new Address(
8 $attributes['address_line_one'],
9 $attributes['address_line_two'],
10 ),
11 set: fn (Address $value) => [
12 'address_line_one' => $value->lineOne,
13 'address_line_two' => $value->lineTwo,
14 ],
15 );
16}

Implicit Route Bindings With Enums

Implicit Enum bindings was contributed by Nuno Maduro.

PHP 8.1 introduces support for Enums. Laravel 9.x introduces the ability to type-hint an Enum on your route definition and Laravel will only invoke the route if that route segment is a valid Enum value in the URI. Otherwise, an HTTP 404 response will be returned automatically. For example, given the following Enum:

1enum Category: string
2{
3 case Fruits = 'fruits';
4 case People = 'people';
5}

You may define a route that will only be invoked if the {category} route segment is fruits or people. Otherwise, an HTTP 404 response will be returned:

1Route::get('/categories/{category}', function (Category $category) {
2 return $category->value;
3});

Controller Route Groups

Route group improvements were contributed by Luke Downing.

You may now use the controller method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke:

1use App\Http\Controllers\OrderController;
2 
3Route::controller(OrderController::class)->group(function () {
4 Route::get('/orders/{id}', 'show');
5 Route::post('/orders', 'store');
6});

Enum Eloquent Attribute Casting

{note} Enum casting is only available for PHP 8.1+.

Enum casting was contributed by Mohamed Said.

Eloquent now allows you to cast your attribute values to PHP enums. To accomplish this, you may specify the attribute and enum you wish to cast in your model's $casts property array:

1use App\Enums\ServerStatus;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'status' => ServerStatus::class,
10];

Once you have defined the cast on your model, the specified attribute will be automatically cast to and from an enum when you interact with the attribute:

1if ($server->status == ServerStatus::provisioned) {
2 $server->status = ServerStatus::ready;
3 
4 $server->save();
5}

Forced Scoped Bindings

Forced scoped bindings was contributed by Claudio Dekker.

In previous releases of Laravel, you may wish to scope the second Eloquent model in a route definition such that it must be a child of the previous Eloquent model. For example, consider this route definition that retrieves a blog post by slug for a specific user:

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
5 return $post;
6});

When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. However, this behavior was only previously supported by Laravel when a custom key was used for the child route binding.

However, in Laravel 9.x, you may now instruct Laravel to scope "child" bindings even when a custom key is not provided. To do so, you may invoke the scopeBindings method when defining your route:

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
5 return $post;
6})->scopeBindings();

Or, you may instruct an entire group of route definitions to use scoped bindings:

1Route::scopeBindings()->group(function () {
2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
3 return $post;
4 });
5});

Laravel Breeze API & Next.js

The Laravel Breeze API scaffolding and Next.js starter kit was contributed by Taylor Otwell and Miguel Piedrafita.

The Laravel Breeze starter kit has received an "API" scaffolding mode and complimentary Next.js frontend implementation. This starter kit scaffolding may be used to jump start your Laravel applications that are serving as a backend, Laravel Sanctum authenticated API for a JavaScript frontend.

Laravel Scout Database Engine

The Laravel Scout database engine was contributed by Taylor Otwell and Dries Vints.

If your application interacts with small to medium sized databases or has a light workload, you may now use Scout's "database" engine instead of a dedicated search service such as Algolia or MeiliSerach. The database engine will use "where like" clauses and full text indexes when filtering results from your existing database to determine the applicable search results for your query.

Full Text Indexes / Where Clauses

Full text indexes and "where" clauses were contributed by Taylor Otwell and Dries Vints.

When using MySQL or PostgreSQL, the fullText method may now be added to column definitions to generate full text indexes:

1$table->text('bio')->fullText();

In addition, the whereFullText and orWhereFullText methods may be used to add full text "where" clauses to a query for columns that have full text indexes. These methods will be transformed into the appropriate SQL for the underlying database system by Laravel. For example, a MATCH AGAINST clause will be generated for applications utilizing MySQL:

1$users = DB::table('users')
2 ->whereFullText('bio', 'web developer')
3 ->get();

Rendering Inline Blade Templates

Sometimes you may need to transform a raw Blade template string into valid HTML. You may accomplish this using the render method provided by the Blade facade. The render method accepts the Blade template string and an optional array of data to provide to the template:

1use Illuminate\Support\Facades\Blade;
2 
3return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

Soketi Echo Server

The Soketi Echo server was developed by Alex Renoki.

Although not exclusive to Laravel 9.x, Laravel has recently assisted with the documentation of Soketi, a Laravel Echo compatible Web Socket server written for Node.js. Soketi provides a great, open source alternative to Pusher and Ably for those applications that prefer to manage their own Web Socket server.

Bootstrap 5 Pagination Views

Laravel now includes pagination views built using Bootstrap 5. To use these views instead of the default Tailwind views, you may call the paginator's useBootstrapFive method within the boot method of your App\Providers\AppServiceProvider class:

1use Illuminate\Pagination\Paginator;
2 
3/**
4 * Bootstrap any application services.
5 *
6 * @return void
7 */
8public function boot()
9{
10 Paginator::useBootstrapFive();
11}

Improved Ignition Exception Page

Ignition, the open source exception debug page created by Spatie, has been redesigned from the ground up. The new, improved Ignition ships with Laravel 9.x and includes light / dark themes, customizable "open in editor" functionality, and more.

New Helpers

Laravel 9.x introduces two new, convenient helper functions that you may use in your own application.

str

The str function returns a new Illuminate\Support\Stringable instance for the given string. This function is equivalent to the Str::of method:

1$string = str('Taylor')->append(' Otwell');
2 
3// 'Taylor Otwell'

If no argument is provided to the str function, the function returns an instance of Illuminate\Support\Str:

1$snake = str()->snake('LaravelFramework');
2 
3// 'laravel_framework'

to_route

The to_route function generates a redirect HTTP response for a given named route, providing an expressive way to redirect to named routes from your routes and controllers:

1return to_route('users.show', ['user' => 1]);

If necessary, you may pass the HTTP status code that should be assigned to the redirect and any additional response headers as the third and fourth arguments to the to_route method:

1return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']);

The server.php file can be removed

A minor feature but you can now remove the server.php file from your project and it will be included inside the framework. This file is only used for php artisan serve.

And More...

Laravel 9 is still a few months away, and more new features and announcements will be coming out. We will be updating this post as those get announced. You can also check out the official release page.

Filed in:

Eric L. Barnes

Eric is the creator of Laravel News and has been covering Laravel since 2012.