Release Notes

Versioning Scheme

Laravel and its other first-party packages follow Semantic Versioning. Major framework releases are released every year (~February), while minor and patch releases may be released as often as every week. Minor and patch releases should never contain breaking changes.

When referencing the Laravel framework or its components from your application or package, you should always use a version constraint such as ^8.0, since major releases of Laravel do include breaking changes. However, we strive to always ensure you may update to a new major release in one day or less.

Exceptions

Named Arguments

At this time, PHP's named arguments functionality are not covered by Laravel's backwards compatibility guidelines. We may choose to rename function parameters when necessary in order to improve the Laravel codebase. Therefore, using named arguments when calling Laravel methods should be done cautiously and with the understanding that the parameter names may change in the future.

Support Policy

For all Laravel releases, bug fixes are provided for 18 months and security fixes are provided for 2 years. For all additional libraries, including Lumen, only the latest major release receives bug fixes. In addition, please review the database versions supported by Laravel.

VersionPHP (*)ReleaseBug Fixes UntilSecurity Fixes Until
6 (LTS)7.2 - 8.0September 3rd, 2019January 25th, 2022September 6th, 2022
77.2 - 8.0March 3rd, 2020October 6th, 2020March 3rd, 2021
87.3 - 8.1September 8th, 2020July 26th, 2022January 24th, 2023
98.0 - 8.1February 8th, 2022August 8th, 2023February 6th, 2024
108.1 - 8.3February 14th, 2023August 6th, 2024February 4th, 2025
End of life
Security fixes only

(*) Supported PHP versions

Laravel 8

Laravel 8 continues the improvements made in Laravel 7.x by introducing Laravel Jetstream, model factory classes, migration squashing, job batching, improved rate limiting, queue improvements, dynamic Blade components, Tailwind pagination views, time testing helpers, improvements to artisan serve, event listener improvements, and a variety of other bug fixes and usability improvements.

Laravel Jetstream

Laravel Jetstream was written by Taylor Otwell.

Laravel Jetstream is a beautifully designed application scaffolding for Laravel. Jetstream provides the perfect starting point for your next project and includes login, registration, email verification, two-factor authentication, session management, API support via Laravel Sanctum, and optional team management. Laravel Jetstream replaces and improves upon the legacy authentication UI scaffolding available for previous versions of Laravel.

Jetstream is designed using Tailwind CSS and offers your choice of Livewire or Inertia scaffolding.

Models Directory

By overwhelming community demand, the default Laravel application skeleton now contains an app/Models directory. We hope you enjoy this new home for your Eloquent models! All relevant generator commands have been updated to assume models exist within the app/Models directory if it exists. If the directory does not exist, the framework will assume your models should be placed within the app directory.

Model Factory Classes

Model factory classes were contributed by Taylor Otwell.

Eloquent model factories have been entirely re-written as class based factories and improved to have first-class relationship support. For example, the UserFactory included with Laravel is written like so:

1<?php
2 
3namespace Database\Factories;
4 
5use App\Models\User;
6use Illuminate\Database\Eloquent\Factories\Factory;
7use Illuminate\Support\Str;
8 
9class UserFactory extends Factory
10{
11 /**
12 * The name of the factory's corresponding model.
13 *
14 * @var string
15 */
16 protected $model = User::class;
17 
18 /**
19 * Define the model's default state.
20 *
21 * @return array
22 */
23 public function definition()
24 {
25 return [
26 'name' => $this->faker->name(),
27 'email' => $this->faker->unique()->safeEmail(),
28 'email_verified_at' => now(),
29 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
30 'remember_token' => Str::random(10),
31 ];
32 }
33}
1<?php
2 
3namespace Database\Factories;
4 
5use App\Models\User;
6use Illuminate\Database\Eloquent\Factories\Factory;
7use Illuminate\Support\Str;
8 
9class UserFactory extends Factory
10{
11 /**
12 * The name of the factory's corresponding model.
13 *
14 * @var string
15 */
16 protected $model = User::class;
17 
18 /**
19 * Define the model's default state.
20 *
21 * @return array
22 */
23 public function definition()
24 {
25 return [
26 'name' => $this->faker->name(),
27 'email' => $this->faker->unique()->safeEmail(),
28 'email_verified_at' => now(),
29 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
30 'remember_token' => Str::random(10),
31 ];
32 }
33}

Thanks to the new HasFactory trait available on generated models, the model factory may be used like so:

1use App\Models\User;
2 
3User::factory()->count(50)->create();
1use App\Models\User;
2 
3User::factory()->count(50)->create();

Since model factories are now simple PHP classes, state transformations may be written as class methods. In addition, you may add any other helper classes to your Eloquent model factory as needed.

For example, your User model might have a suspended state that modifies one of its default attribute values. You may define your state transformations using the base factory's state method. You may name your state method anything you like. After all, it's just a typical PHP method:

1/**
2 * Indicate that the user is suspended.
3 *
4 * @return \Illuminate\Database\Eloquent\Factories\Factory
5 */
6public function suspended()
7{
8 return $this->state([
9 'account_status' => 'suspended',
10 ]);
11}
1/**
2 * Indicate that the user is suspended.
3 *
4 * @return \Illuminate\Database\Eloquent\Factories\Factory
5 */
6public function suspended()
7{
8 return $this->state([
9 'account_status' => 'suspended',
10 ]);
11}

After defining the state transformation method, we may use it like so:

1use App\Models\User;
2 
3User::factory()->count(5)->suspended()->create();
1use App\Models\User;
2 
3User::factory()->count(5)->suspended()->create();

As mentioned, Laravel 8's model factories contain first class support for relationships. So, assuming our User model has a posts relationship method, we may simply run the following code to generate a user with three posts:

1$users = User::factory()
2 ->hasPosts(3, [
3 'published' => false,
4 ])
5 ->create();
1$users = User::factory()
2 ->hasPosts(3, [
3 'published' => false,
4 ])
5 ->create();

To ease the upgrade process, the laravel/legacy-factories package has been released to provide support for the previous iteration of model factories within Laravel 8.x.

Laravel's re-written factories contain many more features that we think you will love. To learn more about model factories, please consult the database testing documentation.

Migration Squashing

Migration squashing was contributed by Taylor Otwell.

As you build your application, you may accumulate more and more migrations over time. This can lead to your migration directory becoming bloated with potentially hundreds of migrations. If you're using MySQL or PostgreSQL, you may now "squash" your migrations into a single SQL file. To get started, execute the schema:dump command:

1php artisan schema:dump
2 
3// Dump the current database schema and prune all existing migrations...
4php artisan schema:dump --prune
1php artisan schema:dump
2 
3// Dump the current database schema and prune all existing migrations...
4php artisan schema:dump --prune

When you execute this command, Laravel will write a "schema" file to your database/schema directory. Now, when you attempt to migrate your database and no other migrations have been executed, Laravel will execute the schema file's SQL first. After executing the schema file's commands, Laravel will execute any remaining migrations that were not part of the schema dump.

Job Batching

Job batching was contributed by Taylor Otwell & Mohamed Said.

Laravel's job batching feature allows you to easily execute a batch of jobs and then perform some action when the batch of jobs has completed executing.

The new batch method of the Bus facade may be used to dispatch a batch of jobs. Of course, batching is primarily useful when combined with completion callbacks. So, you may use the then, catch, and finally methods to define completion callbacks for the batch. Each of these callbacks will receive an Illuminate\Bus\Batch instance when they are invoked:

1use App\Jobs\ProcessPodcast;
2use App\Models\Podcast;
3use Illuminate\Bus\Batch;
4use Illuminate\Support\Facades\Bus;
5use Throwable;
6 
7$batch = Bus::batch([
8 new ProcessPodcast(Podcast::find(1)),
9 new ProcessPodcast(Podcast::find(2)),
10 new ProcessPodcast(Podcast::find(3)),
11 new ProcessPodcast(Podcast::find(4)),
12 new ProcessPodcast(Podcast::find(5)),
13])->then(function (Batch $batch) {
14 // All jobs completed successfully...
15})->catch(function (Batch $batch, Throwable $e) {
16 // First batch job failure detected...
17})->finally(function (Batch $batch) {
18 // The batch has finished executing...
19})->dispatch();
20 
21return $batch->id;
1use App\Jobs\ProcessPodcast;
2use App\Models\Podcast;
3use Illuminate\Bus\Batch;
4use Illuminate\Support\Facades\Bus;
5use Throwable;
6 
7$batch = Bus::batch([
8 new ProcessPodcast(Podcast::find(1)),
9 new ProcessPodcast(Podcast::find(2)),
10 new ProcessPodcast(Podcast::find(3)),
11 new ProcessPodcast(Podcast::find(4)),
12 new ProcessPodcast(Podcast::find(5)),
13])->then(function (Batch $batch) {
14 // All jobs completed successfully...
15})->catch(function (Batch $batch, Throwable $e) {
16 // First batch job failure detected...
17})->finally(function (Batch $batch) {
18 // The batch has finished executing...
19})->dispatch();
20 
21return $batch->id;

To learn more about job batching, please consult the queue documentation.

Improved Rate Limiting

Rate limiting improvements were contributed by Taylor Otwell.

Laravel's request rate limiter feature has been augmented with more flexibility and power, while still maintaining backwards compatibility with previous release's throttle middleware API.

Rate limiters are defined using the RateLimiter facade's for method. The for method accepts a rate limiter name and a closure that returns the limit configuration that should apply to routes that are assigned this rate limiter:

1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Support\Facades\RateLimiter;
3 
4RateLimiter::for('global', function (Request $request) {
5 return Limit::perMinute(1000);
6});
1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Support\Facades\RateLimiter;
3 
4RateLimiter::for('global', function (Request $request) {
5 return Limit::perMinute(1000);
6});

Since rate limiter callbacks receive the incoming HTTP request instance, you may build the appropriate rate limit dynamically based on the incoming request or authenticated user:

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100);
5});
1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100);
5});

Sometimes you may wish to segment rate limits by some arbitrary value. For example, you may wish to allow users to access a given route 100 times per minute per IP address. To accomplish this, you may use the by method when building your rate limit:

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100)->by($request->ip());
5});
1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100)->by($request->ip());
5});

Rate limiters may be attached to routes or route groups using the throttle middleware. The throttle middleware accepts the name of the rate limiter you wish to assign to the route:

1Route::middleware(['throttle:uploads'])->group(function () {
2 Route::post('/audio', function () {
3 //
4 });
5 
6 Route::post('/video', function () {
7 //
8 });
9});
1Route::middleware(['throttle:uploads'])->group(function () {
2 Route::post('/audio', function () {
3 //
4 });
5 
6 Route::post('/video', function () {
7 //
8 });
9});

To learn more about rate limiting, please consult the routing documentation.

Improved Maintenance Mode

Maintenance mode improvements were contributed by Taylor Otwell with inspiration from Spatie.

In previous releases of Laravel, the php artisan down maintenance mode feature may be bypassed using an "allow list" of IP addresses that were allowed to access the application. This feature has been removed in favor of a simpler "secret" / token solution.

While in maintenance mode, you may use the secret option to specify a maintenance mode bypass token:

1php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"
1php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"

After placing the application in maintenance mode, you may navigate to the application URL matching this token and Laravel will issue a maintenance mode bypass cookie to your browser:

1https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515
1https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515

When accessing this hidden route, you will then be redirected to the / route of the application. Once the cookie has been issued to your browser, you will be able to browse the application normally as if it was not in maintenance mode.

Pre-Rendering The Maintenance Mode View

If you utilize the php artisan down command during deployment, your users may still occasionally encounter errors if they access the application while your Composer dependencies or other infrastructure components are updating. This occurs because a significant part of the Laravel framework must boot in order to determine your application is in maintenance mode and render the maintenance mode view using the templating engine.

For this reason, Laravel now allows you to pre-render a maintenance mode view that will be returned at the very beginning of the request cycle. This view is rendered before any of your application's dependencies have loaded. You may pre-render a template of your choice using the down command's render option:

1php artisan down --render="errors::503"
1php artisan down --render="errors::503"

Closure Dispatch / Chain catch

Catch improvements were contributed by Mohamed Said.

Using the new catch method, you may now provide a closure that should be executed if a queued closure fails to complete successfully after exhausting all of your queue's configured retry attempts:

1use Throwable;
2 
3dispatch(function () use ($podcast) {
4 $podcast->publish();
5})->catch(function (Throwable $e) {
6 // This job has failed...
7});
1use Throwable;
2 
3dispatch(function () use ($podcast) {
4 $podcast->publish();
5})->catch(function (Throwable $e) {
6 // This job has failed...
7});

Dynamic Blade Components

Dynamic Blade components were contributed by Taylor Otwell.

Sometimes you may need to render a component but not know which component should be rendered until runtime. In this situation, you may now use Laravel's built-in dynamic-component component to render the component based on a runtime value or variable:

1<x-dynamic-component :component="$componentName" class="mt-4" />
1<x-dynamic-component :component="$componentName" class="mt-4" />

To learn more about Blade components, please consult the Blade documentation.

Event Listener Improvements

Event listener improvements were contributed by Taylor Otwell.

Closure based event listeners may now be registered by only passing the closure to the Event::listen method. Laravel will inspect the closure to determine which type of event the listener handles:

1use App\Events\PodcastProcessed;
2use Illuminate\Support\Facades\Event;
3 
4Event::listen(function (PodcastProcessed $event) {
5 //
6});
1use App\Events\PodcastProcessed;
2use Illuminate\Support\Facades\Event;
3 
4Event::listen(function (PodcastProcessed $event) {
5 //
6});

In addition, closure based event listeners may now be marked as queueable using the Illuminate\Events\queueable function:

1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4 
5Event::listen(queueable(function (PodcastProcessed $event) {
6 //
7}));
1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4 
5Event::listen(queueable(function (PodcastProcessed $event) {
6 //
7}));

Like queued jobs, you may use the onConnection, onQueue, and delay methods to customize the execution of the queued listener:

1Event::listen(queueable(function (PodcastProcessed $event) {
2 //
3})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
1Event::listen(queueable(function (PodcastProcessed $event) {
2 //
3})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

If you would like to handle anonymous queued listener failures, you may provide a closure to the catch method while defining the queueable listener:

1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4use Throwable;
5 
6Event::listen(queueable(function (PodcastProcessed $event) {
7 //
8})->catch(function (PodcastProcessed $event, Throwable $e) {
9 // The queued listener failed...
10}));
1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4use Throwable;
5 
6Event::listen(queueable(function (PodcastProcessed $event) {
7 //
8})->catch(function (PodcastProcessed $event, Throwable $e) {
9 // The queued listener failed...
10}));

Time Testing Helpers

Time testing helpers were contributed by Taylor Otwell with inspiration from Ruby on Rails.

When testing, you may occasionally need to modify the time returned by helpers such as now or Illuminate\Support\Carbon::now(). Laravel's base feature test class now includes helpers that allow you to manipulate the current time:

1public function testTimeCanBeManipulated()
2{
3 // Travel into the future...
4 $this->travel(5)->milliseconds();
5 $this->travel(5)->seconds();
6 $this->travel(5)->minutes();
7 $this->travel(5)->hours();
8 $this->travel(5)->days();
9 $this->travel(5)->weeks();
10 $this->travel(5)->years();
11 
12 // Travel into the past...
13 $this->travel(-5)->hours();
14 
15 // Travel to an explicit time...
16 $this->travelTo(now()->subHours(6));
17 
18 // Return back to the present time...
19 $this->travelBack();
20}
1public function testTimeCanBeManipulated()
2{
3 // Travel into the future...
4 $this->travel(5)->milliseconds();
5 $this->travel(5)->seconds();
6 $this->travel(5)->minutes();
7 $this->travel(5)->hours();
8 $this->travel(5)->days();
9 $this->travel(5)->weeks();
10 $this->travel(5)->years();
11 
12 // Travel into the past...
13 $this->travel(-5)->hours();
14 
15 // Travel to an explicit time...
16 $this->travelTo(now()->subHours(6));
17 
18 // Return back to the present time...
19 $this->travelBack();
20}

Artisan serve Improvements

Artisan serve improvements were contributed by Taylor Otwell.

The Artisan serve command has been improved with automatic reloading when environment variable changes are detected within your local .env file. Previously, the command had to be manually stopped and restarted.

Tailwind Pagination Views

The Laravel paginator has been updated to use the Tailwind CSS framework by default. Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override. Of course, Bootstrap 3 and 4 views remain available as well.

Routing Namespace Updates

In previous releases of Laravel, the RouteServiceProvider contained a $namespace property. This property's value would automatically be prefixed onto controller route definitions and calls to the action helper / URL::action method. In Laravel 8.x, this property is null by default. This means that no automatic namespace prefixing will be done by Laravel. Therefore, in new Laravel 8.x applications, controller route definitions should be defined using standard PHP callable syntax:

1use App\Http\Controllers\UserController;
2 
3Route::get('/users', [UserController::class, 'index']);
1use App\Http\Controllers\UserController;
2 
3Route::get('/users', [UserController::class, 'index']);

Calls to the action related methods should use the same callable syntax:

1action([UserController::class, 'index']);
2 
3return Redirect::action([UserController::class, 'index']);
1action([UserController::class, 'index']);
2 
3return Redirect::action([UserController::class, 'index']);

If you prefer Laravel 7.x style controller route prefixing, you may simply add the $namespace property into your application's RouteServiceProvider.

exclamation

This change only affects new Laravel 8.x applications. Applications upgrading from Laravel 7.x will still have the $namespace property in their RouteServiceProvider.

Comments

No Comments Yet

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.