版本資訊
版本策略
Laravel 及其第一方套件都遵守 語義化版本。框架的主要更新會每年釋出 (約在二月時),而次版本與修訂版則可能頻繁到每週更新。次版本與修訂版 絕對不會 包含中斷性變更。
由於 Laravel 的主要更新會包含中斷性變更,因此在專案或套件中參照 Laravel 框架或其組件時,應使用如 ^8.0
這樣的版本限制式。不過,我們也會不斷努力確保每次進行主要版本更新時,都可於一天之內升級完成。
例外
帶名稱的引數
截至目前為止,PHP 的帶名稱引數 尚未包含在 Laravel 的向下相容性方針內。我們可能會在有必要的時候更改函式的引數名稱以改進 Laravel 的程式碼。因此,在使用帶名稱引數呼叫 Laravel 方法時應格外注意,並應瞭解到引數名稱未來可能會有所更改。
支援政策
所有的 Laravel 版本都提供 18 個月的 Bug 修正,以及 2 年的安全性修正。對於其他的函式庫,如 Lumen,則只有最新的主要版本會收到 Bug 修正。此外,也請參考 Laravel 支援的資料庫版本。
版本 | PHP (*) | 釋出日期 | Bug 修正期限 | 安全性修正期限 |
---|---|---|---|---|
6 (LTS) | 7.2 - 8.0 | 2019 年 9 月 3 日 | 2022 年 1 月 25 日 | 2022 年 9 月 6 日 |
7 | 7.2 - 8.0 | 2020 年 3 月 3 日 | 2020 年 10 月 6 日 | 2021 年 3 月 3 日 |
8 | 7.3 - 8.1 | 2020 年 9 月 8 日 | 2022 年 7 月 26 日 | 2023 年 1 月 24 日 |
9 | 8.0 - 8.1 | 2022 年 2 月 8 日 | 2023 年 8 月 8 日 | 2024 年 2 月 6 日 |
10 | 8.1 - 8.3 | 2023 年 2 月 14 日 | 2024 年 8 月 6 日 | 2025 年 2 月 4 日 |
Laravel 8
Laravel 8 持續地對 Laravel 7.x 進行改進,包含導入了 Laravel Jetstream、模型 Factory 類別、資料庫遷移壓縮、批次任務、改進頻率限制、佇列改進、動態 Blade 元件、Tailwind 分頁檢視器、測試時間用的輔助函式、對 artisan serve
的改進、時間監聽程式改進、以及各種其他 Bug 修正以及使用性改進。
Laravel Jetstream
Laravel Jetstream 由 Taylor Otwell 撰寫。
Laravel Jetstream 是一套用於 Laravel 的網站 Scaffolding,有漂亮的設計。Jetstream 為你的下一個專案提供了一個絕佳的開始點,包含登入、註冊、電子郵件認證、二步驟認證、Session 管理、通過 Laravel Sanctum 提供的 API 支援、以及選配的團隊管理。Laravel Jetstream 取代並改進了過往版本 Laravel 所提供的舊版認證 UI Scaffolding。
Jetstream 是使用 Tailwind CSS 進行設計的,並提供了Livewire 或 Inertia Scaffolding 可進行選擇。
Model 目錄
為了回應來自社群的強烈要求,Laravel 專案的預設基本架構目前已包含了 app/Models
目錄。我們希望你能享受這個 Eloquent Model 的新家!所有相關的產生程式指令都已更新。而且,如果 app/Models
目錄存在,那麼這些產生程式會假設這個資料夾是用來存放 Model 的。若該目錄不存在,則框架會假設 Model 應放置於 app
目錄內。
Model Factory 類別
Model Factory 類別由 Taylor Otwell 參與貢獻。
Eloquent 的 Model Factory 已經全面重寫為基於 Class 的 Factory 了,並且也經過改進來直接支援資料庫關聯。舉例來說,在 Laravel 中的 UserFactory
是這樣寫的:
1<?php23namespace Database\Factories;45use App\Models\User;6use Illuminate\Database\Eloquent\Factories\Factory;7use Illuminate\Support\Str;89class UserFactory extends Factory10{11 /**12 * The name of the factory's corresponding model.13 *14 * @var string15 */16 protected $model = User::class;1718 /**19 * Define the model's default state.20 *21 * @return array22 */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', // password30 'remember_token' => Str::random(10),31 ];32 }33}
1<?php23namespace Database\Factories;45use App\Models\User;6use Illuminate\Database\Eloquent\Factories\Factory;7use Illuminate\Support\Str;89class UserFactory extends Factory10{11 /**12 * The name of the factory's corresponding model.13 *14 * @var string15 */16 protected $model = User::class;1718 /**19 * Define the model's default state.20 *21 * @return array22 */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', // password30 'remember_token' => Str::random(10),31 ];32 }33}
由於產生的 Model 中包含了新的 HasFactory
Trait,因此我們可以這樣使用 Model Factory:
1use App\Models\User;23User::factory()->count(50)->create();
1use App\Models\User;23User::factory()->count(50)->create();
由於 Model Factory 已經是一般的 PHP 類別了,因此 State 的變換應通過類別方法來撰寫。此外,也可以依照需求在 Eloquent Model Factory 內加上任何其他的輔助函式。
舉例來說,User
Model 可能會有個 suspended
狀態,用於修改 Model 中預設的屬性值。可以通過基礎 Factory 的 state
方法來定義狀態變換。可以任意為狀態方法命名。不管怎麼樣,這個方法就只是個單純的 PHP 方法而已:
1/**2 * Indicate that the user is suspended.3 *4 * @return \Illuminate\Database\Eloquent\Factories\Factory5 */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\Factory5 */6public function suspended()7{8 return $this->state([9 'account_status' => 'suspended',10 ]);11}
定義好狀態變換方法後,我們可以這樣使用:
1use App\Models\User;23User::factory()->count(5)->suspended()->create();
1use App\Models\User;23User::factory()->count(5)->suspended()->create();
就像前面提到的一樣,Laravel 8 的 Model Factory 包含了對關聯的第一手支援。因此,假設我們的 User
Model 有個 posts
關聯方法,我們只需要執行下列程式碼就能產生一個有 3 篇貼文的使用者:
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();
為了減緩升級的過程,我們提供了 laravel/legacy-factories 套件來在 Laravel 8.x 中提供舊版 Model Factory 的支援。
Laravel 的全新 Factory 包含了其他更多我們認為你會喜歡的功能。要瞭解更多有關 Model Factory 的資訊,請參考資料庫測試說明文件。
資料庫遷移壓縮
Migration 壓縮由 Taylor Otwell 參與貢獻。
在寫網站的時候,我們可能會逐漸累積出越來越多的資料庫遷移檔。這樣可能會導致遷移檔目錄中被數百個遷移檔給佔滿。若你使用 MySQL 或 PostgreSQL,現在可以將遷移檔「壓縮」進單一 SQL 檔內。要開始壓縮,請執行 schema:dump
指令:
1php artisan schema:dump23// Dump the current database schema and prune all existing migrations...4php artisan schema:dump --prune
1php artisan schema:dump23// Dump the current database schema and prune all existing migrations...4php artisan schema:dump --prune
執行該指令時,Laravel 會將一個「結構描述 (Schema)」檔案寫入 database/schema
目錄內。接著,當要遷移資料庫且尚未執行過任何遷移時,Laravel 會先執行該結構描述檔的 SQL。執行玩結構描述檔的指令後,Laravel 才會接著執行不在該結構描述傾印中剩下的遷移。
批次任務
批次任務由 Taylor Otwell & Mohamed Said 參與貢獻。
Laravel 的批次任務功能能讓你輕鬆地執行一系列的任務,並接著在這些任務完成後執行其他操作。
Bus
Facade 的全新 batch
方法可以用來分派一批任務。當然,批次功能與完成回呼一起使用時是最有用。因此,可以使用 then
, catch
與 finally
方法來為該批次定義完成回呼。這些回呼都會在被叫用時收到 Illuminate\Bus\Batch
實體:
1use App\Jobs\ProcessPodcast;2use App\Models\Podcast;3use Illuminate\Bus\Batch;4use Illuminate\Support\Facades\Bus;5use Throwable;67$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();2021return $batch->id;
1use App\Jobs\ProcessPodcast;2use App\Models\Podcast;3use Illuminate\Bus\Batch;4use Illuminate\Support\Facades\Bus;5use Throwable;67$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();2021return $batch->id;
要瞭解更多有關批次任務的資訊,請參考佇列說明文件。
改進的頻率限制
(頻率限制的改進由 Taylor Otwell 參與貢獻*。
Laravel 的請求頻率限制功能現在有了更多的彈性與能力,且仍於過去版本的 throttle
中間層 API 保持向下相容性。
可以使用 RateLimiter
Facade 的 for
方法來定義頻率限制程式。for
方法接收頻率限制程式的名稱、以及一個閉包。該閉包應回傳頻率限制的設定,該設定將套用到有設定這個頻率限制程式的路由上:
1use Illuminate\Cache\RateLimiting\Limit;2use Illuminate\Support\Facades\RateLimiter;34RateLimiter::for('global', function (Request $request) {5 return Limit::perMinute(1000);6});
1use Illuminate\Cache\RateLimiting\Limit;2use Illuminate\Support\Facades\RateLimiter;34RateLimiter::for('global', function (Request $request) {5 return Limit::perMinute(1000);6});
由於頻率限制程式的回呼會接收連入 HTTP 請求實體,因此我們可以依據連入請求或登入使用者來動態調整適當的頻率限制:
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});
有時候,我們可能會像以某些任意數值來設定頻率限制。舉例來說,我們可能會想限制給定的路由:每個 IP 位址每分鐘只能存取 100 次。為此,可以在設定頻率限制時使用 by
方法:
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});
可以使用 throttle
Middleware 來將頻率限制程式附加到路由或路由群組上。這個 Throttle Middleware 接受欲指派給路由的頻率限制程式名稱:
1Route::middleware(['throttle:uploads'])->group(function () {2 Route::post('/audio', function () {3 //4 });56 Route::post('/video', function () {7 //8 });9});
1Route::middleware(['throttle:uploads'])->group(function () {2 Route::post('/audio', function () {3 //4 });56 Route::post('/video', function () {7 //8 });9});
要瞭解更多有關頻率限制的資訊,請參考路由說明文件。
改進過的維護模式
改進過的維護模式由 Taylor Otwell 參與貢獻,靈感來自 Spatie。
在之前版本的 Laravel 中,php artisan down
維護模式功能可以通過使用一組允許存取網站的 IP 位址「允許列表」來繞過。該功能現已被移除,並改用了一種更簡單的「密鑰」/ 權杖方案來代替。
在維護模式下,可以使用 secret
選項來指定一個用來繞過維護模式的權杖:
1php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"
1php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"
將應用程式放入維護模式後,可以瀏覽符合該權杖的應用程式網址,Laravel 會簽發一個繞過維護模式的 Cookie 給瀏覽器:
1https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515
1https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515
在存取該隱藏路由時,會接著被重新導向至應用程式的 /
路由。該 Cookie 被簽發給瀏覽器後,就可以像沒有在維護模式一樣正常地瀏覽應用程式。
預轉譯維護模式 View
若在部署過程中使用了 php artisan down
指令,若使用者在 Composer 依賴或其他基礎設施元件更新時存取了應用程式,則可能會遇到錯誤。這是因為 Laravel 框架中重要的部分必須要先啟動才能判斷應用程式是否在維護模式下,並才能接著使用樣板引擎來轉譯維護模式的 View。
基於此原因,現在,Laravel 能讓你預先轉譯維護模式 View,並在整個請求週期的一開始就將其回傳。這個 View 會在任何應用程式的依賴載入前就預先被轉譯。可以使用 down
指令的 render
選項來預轉譯所選的樣板:
1php artisan down --render="errors::503"
1php artisan down --render="errors::503"
閉包分派與顆串連的 catch
Catch 的改進由 Mohamed Said 參與貢獻。
使用新的 catch
方法,就能為佇列閉包提供一組要在所有重試次數都失敗的時候執行的閉包:
1use Throwable;23dispatch(function () use ($podcast) {4 $podcast->publish();5})->catch(function (Throwable $e) {6 // This job has failed...7});
1use Throwable;23dispatch(function () use ($podcast) {4 $podcast->publish();5})->catch(function (Throwable $e) {6 // This job has failed...7});
動態 Blade 元件
動態 Blade 元件由 Taylor Otwell 參與貢獻。
有時候我們可能會需要轉譯元件,但在執行階段前並不知道要轉譯哪個元件。對於這種情況,現在,我們可以使用 Laravel 的內建「dynamic-component」動態元件來依照執行階段的值或變數進行轉譯:
1<x-dynamic-component :component="$componentName" class="mt-4" />
1<x-dynamic-component :component="$componentName" class="mt-4" />
要瞭解更多有關 Blade 元件的資訊,請參考 Blade 的說明文件。
事件監聽程式的改進
Event Listener 的改進由 Taylor Otwell 參與貢獻。
現在,只要將閉包傳給 Event::listen
方法,就可以註冊基於閉包的事件監聽程式。Laravel 會偵測閉包,以判斷該事件監聽程式能負責的事件類型:
1use App\Events\PodcastProcessed;2use Illuminate\Support\Facades\Event;34Event::listen(function (PodcastProcessed $event) {5 //6});
1use App\Events\PodcastProcessed;2use Illuminate\Support\Facades\Event;34Event::listen(function (PodcastProcessed $event) {5 //6});
此外,可以使用 Illuminate\Events\queueable
方法來將基於閉包的事件監聽程式標記為要放入佇列 (Queueable):
1use App\Events\PodcastProcessed;2use function Illuminate\Events\queueable;3use Illuminate\Support\Facades\Event;45Event::listen(queueable(function (PodcastProcessed $event) {6 //7}));
1use App\Events\PodcastProcessed;2use function Illuminate\Events\queueable;3use Illuminate\Support\Facades\Event;45Event::listen(queueable(function (PodcastProcessed $event) {6 //7}));
就像佇列任務一樣,可以使用 onConnection
, onQueue
, 與 delay
方法來自訂放入佇列的監聽程式的執行:
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)));
若想在匿名的佇列監聽程式執行失敗時進行處理,可以在定義 queueable
監聽程式時提供一個閉包給 catch
方法:
1use App\Events\PodcastProcessed;2use function Illuminate\Events\queueable;3use Illuminate\Support\Facades\Event;4use Throwable;56Event::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;56Event::listen(queueable(function (PodcastProcessed $event) {7 //8})->catch(function (PodcastProcessed $event, Throwable $e) {9 // The queued listener failed...10}));
時間測試輔助函式
時間測試輔助韓式由 Taylor Otwell 參與貢獻,靈感來自 Ruby on Rails。
在測試的時候,我們有時候會想要更改如 now
或 Illuminate\Support\Carbon::now()
等輔助函式所回傳的時間。現在,Laravel 的基礎功能測試 (Feature Test) 類別已包含了顆用來更改目前時間的輔助函式:
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();1112 // Travel into the past...13 $this->travel(-5)->hours();1415 // Travel to an explicit time...16 $this->travelTo(now()->subHours(6));1718 // 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();1112 // Travel into the past...13 $this->travel(-5)->hours();1415 // Travel to an explicit time...16 $this->travelTo(now()->subHours(6));1718 // Return back to the present time...19 $this->travelBack();20}
Artisan serve
的改進
Artisan serve
的改進由 Taylor Otwell 參與貢獻。
Artisan serve
指令已經過改進,該指令會偵測本機的 .env
檔案,並在環境變數更改的時候自動重新載入。在此之前,需要手動停止並重新啟動該指令。
Tailwind 分頁 View
Laravel 的分頁程式 (Paginator) 已更新為預設使用 Tailwind CSS 框架。Tailwind CSS 是一個可高度客製化、低階的 CSS 框架,能讓你不需處理並複寫一些煩人的固定樣式,就能製作所有你所需要的客製化區塊。當然,Bootstrap 3 與 Bootstrap 4 的 View 依然可用。
路由 Namespace 更新
在之前的 Laravel 版本中,RouteServiceProvider
包含了一個 $namespace
屬性。當使用 Controller 路由定義或是呼叫 action
輔助函式 / URL::action
方法時,會自動將該屬性的值加到前面。在 Laravel 8.x 中,這個屬性預設為 null
。這表示,Laravel 將不會自動幫你將 Namespace 放在前面。因此,在新安裝的 Laravel 8.x 專案中,Controller 路由定義應使用標準的 PHP Callable 語法來定義:
1use App\Http\Controllers\UserController;23Route::get('/users', [UserController::class, 'index']);
1use App\Http\Controllers\UserController;23Route::get('/users', [UserController::class, 'index']);
與呼叫 actions
相關的方法也應使用相同的 Callable 語法:
1action([UserController::class, 'index']);23return Redirect::action([UserController::class, 'index']);
1action([UserController::class, 'index']);23return Redirect::action([UserController::class, 'index']);
若你偏好使用 Laravel 7.x 風格的 Controller 路由前置,只需要在專案的 RouteServiceProvider
中加上 $namespace
屬性即可。
This change only affects new Laravel 8.x applications. Applications upgrading from Laravel 7.x will still have the $namespace
property in their RouteServiceProvider
.