« back published by @mmartin_joo on February 28, 2024

What's new in Laravel 11?

Laravel 11 will come out soon, and it will have some interesting changes. One of the biggest changes, of course, is the new simplified folder structure.

You can already install it by running the following command:

laravel new myapp --dev

Or using composer:

composer create-project --prefer-dist laravel/laravel myapp dev-master

New folder structure

No Http/Middleware folder

In older Laravel versions there was a Http/Middleware folder with some default middlewares such as:

  • EncryptCookies
  • TrimStrings
  • RedirectIfAuthenticated
  • etc

Now they are part of the framework instead of being in the Http/Middleware folder in your project. Of course, when you create a new middleware with the php artisan make:middleware command, it will still be created in the Http/Middleware folder.

No Http/Kernel.php file

So you created a new Middlaware and you want to add it to your API routes. You would go to the Http/Kernel.php file just to realize it's not there anymore.

Now all Middlware configuration is done in the bootstrap/app.php file:

<?php

use App\Http\Middleware\ExampleMiddleware;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
  ->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
  )
  ->withMiddleware(function (Middleware $middleware) {
    // Apply middleware to all routes
    $middleware->use([ExampleMiddleware::class]);
      
    // Use it only in the web routes
    $middleware->web([ExampleMiddleware::class]);
        
    // API only
    $middleware->api([ExampleMiddleware::class]);
  })
  ->withExceptions(function (Exceptions $exceptions) {
    //
  })->create();

As you can see, there's a new withMiddleware method where you can configure your middlewares.

No RouteServiceProvider.php file

By default, Laravel 11 will not have a RouteServiceProvider.php file. So how can we configure our routes?

Let's say we want to add API routes with a prefix of /api. In previous Laravel versions this was done in the RouteServiceProvider.php file:

public function boot()
{
  $this->routes(function () {
    Route::middleware('api')
      ->prefix('api')
      ->group(base_path('routes/api.php'));
  });
}

Now in Laravel 11, we need to use the bootstrap/app.php file again:

return Application::configure(basePath: dirname(__DIR__))
  ->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    apiPrefix: '/api',
  )

We can check if it works by running the php artisan route:list command:

Laravel 11

No Console/Kernel.php file

In previous Laravel versions commands were loaded in the Console/Kernel.php file. This was also the place to write our task scheduling logic.

Now, in Laravel 11 task scheduling can be done in the routes/console.php file:

use App\Console\Commands\ExampleCommand;
use App\Jobs\ExampleJob;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote')->hourly();

Schedule::call(function () {
    dd('hey');
})->daily();

Schedule::command(ExampleCommand::class)->daily();

Schedule::command('app:example-command')->daily();

Schedule::job(ExampleJob::class)->daily();

We can use the same methods for scheduling:

  • call to schedule a callback
  • command to schedule a Command
  • job to schedule a Job

Less config files by default

In a new Laravel 11 project we have less config files by default in the config directory. Some of them lives inside the framework, and they get merged, so we only need to add the ones we want to override.

These are the config files included by default:

Laravel 11

If you need some of the older configs, such as broadcasting.php you can publish it by running:

php artisan config:publish broadcasting

And now you have the broadcasting.php file in your config directory.

Eager load limit

I think this is the best new feature in Laravel 11. We can limit the number of records loaded when using eager load:

Post::query()
  ->with([
    'comments' => fn ($query) => $query
      ->orderBy('created_at', 'desc')
      ->limit(10)
  ])->get();

This will load the 10 most recent comments for each post. It's a pretty handy feature as it helps you avoid performance issues.

You can do the same in earlier Laravel versions as well, but you need to install the staudenmeir/eloquent-eager-limit package. In fact, this exact package was merged into Laravel 11.

New casts method

In Laravel 11 there's a new method called casts that you can use to cast your model attributes. The old $casts property is still there, but the method will take precedence if both are present.

Since now it's a method we can do more stuff:

protected function casts(): array
{
  return [
    'publish_at' => 'datetime',
    'statuses' => AsEnumCollection::of(PostStatus::class),
    'foo' => function () {
      // Your own logic
    },
  ];
}

Although they are not new, but here are some interesting Model casts you might not know about:

  • AsEnumCollection to store multiple enum values in a single column
  • AsArrayObject to store and access an associative array easily since the standard array cast work only with primitive arrays
  • AsCollection to cast values to a Laravel collection instead of a PHP array

SQLite

The default database driver is now SQLite. I'm not sure if I love this change but of course it's pretty easy to change it to MySQL in the .env file.

once

There's a new function called once which can be used to memoize functions.

Memoization is an optimization technique to speed up the execution of functions by caching the results of expensive calls and returning the cached result when the same inputs occur again. Basically, it's like a Redis a DB cache but for your functions.

Here's how memoization works:

  • Function call: when a function is called with certain inputs, the function checks if the result for those inputs is already cached.
  • Caching results: if the result is not cached, the function calculates the result and stores it in a data structure indexed by the input parameters.
  • Returning cached result: on subsequent calls with the same inputs, instead of recalculating the result, the function retrieves the cached result from the data structure and returns it.

Memoization is particularly useful for optimizing recursive functions or functions with repeated computations. By storing previously computed results, it reduces redundant calculations and improves the performance of the program.

I don't think it's something that we use on a daily basis in high level web apps, but it's a nice addition to the framework.

The usage is pretty simple as always:

private function hashPassword(string $password)
{
  $pass = once(fn () => bcrypt($password));

  var_dump('Hashed password: ' . $pass);

  return $pass;
}

For this example, I added a var_dump statement to the bcrypt function to see how many times it's called.

Here's some code to test if it works:

public function handle()
{
  $this->hashPassword('password');
  $this->hashPassword('password');
  $this->hashPassword('password');
  $this->hashPassword('password');
  $this->hashPassword('my-strong-pass');
}

And here's the output: Laravel 11

As you can see, the bcrypt function is called only twice:

  • Once with the input of password
  • And one more time with the input of my-strong-pass

So all the subsequent calls with the same input will return the cached result.