« back published by Martin 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:
EncryptCookiesTrimStringsRedirectIfAuthenticated- 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:
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:
callto schedule a callbackcommandto schedule a Commandjobto 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:
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.
Eloquent-related changes
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:
AsEnumCollectionto store multiple enum values in a single columnAsArrayObjectto store and access an associative array easily since the standardarraycast work only with primitive arraysAsCollectionto 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:

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.