Laravel 9.0 released – what’s new?

Paweł Kamiński - Senior Full Stack Developer
5 minutes read

For most PHP programmers, the beginning of February should be clearly associated with the new version of the Laravel framework. Last year in December, we managed to show you what new products will be presented with the release of version 9.0 of this popular tool. Today we will try to compare the information we had at that time with what is contained in the actually released version.

A quick reminder

By looking at the above-mentioned article, you can already confirm the elements that were actually introduced into the new version:

  • PHP 8 as a minimal interpreter version working with Laravel 9
  • Anonymous migrations
  • New Query Builder interface
  • Text functions
  • Switching from SwiftMailer to Symfony Mailer

For details of the above-mentioned issues, please see our previous post. Now let's try to focus on new products that we did not know about in December 2021.

What's new in Eloquent Acessors and Mutators

The new version of Laravel provides a new way to define accessors and mutators. So far, their use has been to define methods in model classes, for example:

public function getNameAttribute($value)
{
   return strtoupper($value);
}
 
public function setNameAttribute($value)
{
   $this->attributes['name'] = $value;
}

Source: https://laravel.com/docs/9.x/releases#eloquent-accessors-and-mutators

In the new version, it is possible to create accessors and mutators using one method where type-hinting is Illuminate\Database\Eloquent\Casts\Attribute. Example:

use Illuminate\Database\Eloquent\Casts\Attribute;
 
public function name(): Attribute
{
   return new Attribute(
       get: fn ($value) => strtoupper($value),
       set: fn ($value) => $value,
   );
}

Source: https://laravel.com/docs/9.x/releases#eloquent-accessors-and-mutators

Enum Eloquent Attribute Casting

Thanks to the capabilities of the PHP interpreter version 8.1, it is possible to cast the values ​​of model attributes to enum objects (new in PHP 8.1). For this purpose, we need to specify the attributes we would like to cast, by creating a binding table and assigning it to the $casts field of the model:

use App\Enums\ServerStatus;
 
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
   'status' => ServerStatus::class,
];

Source: https://laravel.com/docs/9.x/releases#enum-casting

Such a modified model causes that calling and using its attributes will result in their projection on the enum:

if ($server->status == ServerStatus::provisioned) {
   $server->status = ServerStatus::ready;
 
   $server->save();
}

Source: https://laravel.com/docs/9.x/releases#enum-casting

Route Bindings With Enums

Thanks to the enums mechanism described above and introduced in PHP 8.1, it is possible to use it in building routing. Let’s take a look at the example of an enum describing categories:

enum Category: string
{
   case Fruits = 'fruits';
   case People = 'people';
}

Source: https://laravel.com/docs/9.x/releases#implicit-route-bindings-with-enums

We can now suggest to create a new route, which will be called only if the part of it described as {category} has the value "fruits" or "people":

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

Source: https://laravel.com/docs/9.x/releases#implicit-route-bindings-with-enums

If this condition is not met, a page with 404 status will be returned.

Forced Scoping Of Route Bindings

When building individual routing entries, there is often a need to associate the model with the appropriate route definition. Previous versions of Laravel allowed to create this type of address even for two models, as long as there was a defined binding between them. The example below illustrates the situation when we try to get all the user's posts based on the "slug" column:

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

Source: https://laravel.com/docs/9.x/releases#forced-scoping-of-route-bindings

As you can see, this code searches for post model objects not on the standard "id" but on the "slug" column. This technique is called "custom keyed implict binding". Its use meant that it recognized the type of connection between the user and post models on the basis of the naming convention. Unfortunately, this only happened if an explicit non-default key was actually used.

In Laravel 9, we have the option to force the framework to recognize the described bindings between entities, even when the custom key is not provided. For this purpose, when building the route, we should use the scopeBindings method:

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

Source: https://laravel.com/docs/9.x/releases#forced-scoping-of-route-bindings

What’s more, in a similar way we can force constraint recognition for whole groups of routes:

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

Source: https://laravel.com/docs/9.x/releases#forced-scoping-of-route-bindings

Controller Route Groups

Another new feature concerns controllers and routing. Laravel 9 introduces the ability to define a common controller for the entire group of routes. In the following example, taken directly from the framework documentation, we can see an example of such a declaration:

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

Source: https://laravel.com/docs/9.x/releases#controller-route-groups/

It is easy to conclude that for both routes/orders/{id} and /orders one OrderController has been defined, which contains two methods - "show" and "store" - they are of course assigned to the selected routes.

Full Text Indexes/Where Clauses

The changes also apply to migration, as the latest version of the framework adds the implementation of "full text" indexes. Their use is extremely simple, as it only requires the added "fullText" method when declaring the column:

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

Thanks to this, it is possible to use the "whereFullText" and "orWhereFullText" methods later:

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

Source: https://laravel.com/docs/9.x/releases#full-text

The above query example will be replaced with the appropriate clause of the selected database. In MySQL it will be "match against".

Rendering Inline Blade Templates

Further changes have been made to the way views are handled. The authors added the ability to generate HTML code based on the Blade code placed in the "render" method. Thanks to this, it is possible to quickly build the resulting HTML in one line:

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

Source: https://laravel.com/docs/9.x/releases#rendering-inline-blade-templates

The render method takes the Blade template code as the first parameter, and the data array as the second parameter. Moreover, it is possible to use the "renderComponent" method, which allows you to pass a class component to be rendered:

use App\View\Components\HelloComponent;
 
return Blade::renderComponent(new HelloComponent('Julian Bashir'));

Source: https://laravel.com/docs/9.x/releases#rendering-inline-blade-templates

Slot Name Shortcut

Some slight improvements also concern the use of slots in views. Their current names are given as the value of the "name" attribute of the x-slot tag, which are presented below:

<x-alert>
   <x-slot name="title">
       Server Error
   </x-slot>
 
   <strong>Whoops!</strong> Something went wrong!
</x-alert>

Source: https://laravel.com/docs/9.x/releases#slot-name-shortcut

are replaced by the abbreviation:

<x-slot:title>
   Server Error
</x-slot>

Source: https://laravel.com/docs/9.x/releases#slot-name-shortcut

Checked/Selected Blade Directives

Another novelty concerns the method of selecting a checkbox and a drop-down list. For the first one, you can now use the "@checked" directive, which, based on a defined logical condition, will select or deselect the check box:

<input type="checkbox"
       name="active"
       value="active"
       @checked(old('active', $user->active)) />

Source: https://laravel.com/docs/9.x/releases#checked-selected-blade-directives

The "@selected" directive can be used in a similar way:

<select name="version">
   @foreach ($product->versions as $version)
       <option value="{{ $version }}" @selected(old('version') == $version)>
           {{ $version }}
       </option>
   @endforeach
</select>

Source: https://laravel.com/docs/9.x/releases#checked-selected-blade-directives

In this example, individual elements of the selection list are added in the loop, and the @selected directive checks the condition that defines which field is to be selected.

Others

In addition to the above-mentioned list, the authors introduced a number of smaller improvements:

  • Pagination views based on Bootstrap 5 – adding the "useBootstrapFive" method that can be called in the "boot" function of the “App\Providers\AppServiceProvider” class.
  • Improved validation of nested arrays – the "Rule" class of the validator received a new "forEach" method, thanks to which it is possible to call a validation function on each element of the array.
  • Improved Ignition bug page – Laravel 9.0 includes the latest version of the Ignition module with several improvements – light and dark skin, and the option to open the bug in the IDE.
  • Changes in the way of displaying the routing list in the CLI – the displayed list is clearer and more transparent.
  • Improved support for collection support in the IDE.
  • Added flag - –coverage and - –min for tests – the "test" command known from earlier versions of Laravel has been improved with two new flags: "coverage", which displays information about the application's test coverage (expressed as a percentage) and “min”, which allows you to declare the percentage of coverage that will allow you to consider the end of the test as correct.

To sum up

Analyzing all of the changes, it can be easily noticed that the next version of the framework is an evolution, not a revolution. In most cases, the changes are an improvement of the functionalities known from previous versions, enriching them with new possibilities or refreshing the appearance. Nevertheless, some of these will definitely become the permanent standards and will improve the programmer's daily work.

It is also worth noting that the planned bug fix support for Laravel version 9.0 ends in August 2023, while all security threats will be fixed by February 2024.

And on February 7, 2023, the next, tenth edition of the framework awaits us. So make sure to follow us on social media – as soon as the first confirmed leaks appear, we will let you know!

On-demand webinar: Moving Forward From Legacy Systems

We’ll walk you through how to think about an upgrade, refactor, or migration project to your codebase. By the end of this webinar, you’ll have a step-by-step plan to move away from the legacy system.

moving forward from legacy systems - webinar

Latest blog posts

See more

Ready to talk about your project?

1.

Tell us more

Fill out a quick form describing your needs. You can always add details later on and we’ll reply within a day!

2.

Strategic Planning

We go through recommended tools, technologies and frameworks that best fit the challenges you face.

3.

Workshop Kickoff

Once we arrange the formalities, you can meet your Polcode team members and we’ll begin developing your next project.