Laravel 一直是我心中最優雅的後端框架,為了向更多的人解釋為什麼 Laravel 這麼優雅?框架本身都做了什麼操作?比起其他框架的優勢在哪裡等?我準備從一個後端最常用的 CURD 例子說起,一步一步闡述這過程中 Laravel 都是怎麼完成的;以及大家(我)為什麼喜歡用 Laravel。
Introduction Laravel
Laravel 的定位是一個全棧 WEB 框架,它提供了 WEB 開發的全套元件;如路由、中介軟體、MVC、ORM、Testing 等。這篇文章中我使用的 Demo 是最新版的 Laravel 10.x 以及 PHP 8.2。雖說從 Laravel 5.x 後 Laravel 的版本變化比較快,基本一年一個大版本,但它的核心幾乎從 4.X 以來沒有發生過特別大的變化。Laravel 的目錄結構可能對第一次接觸的人來說會很繁瑣,它有十來個資料夾,但其實大部分資料夾的位置都是精心設計的,都待在應該待的位置上。
Laravel Artisan
Laravel 第一個優雅的設計就是給開發者暴露了一個 ALLINONE 的入口 ———Artisan
。Artisan 是一個 SHELL 指令碼,是透過命令列操作 Laravel 的唯一入口。所有和 Laravel 的互動包括操作佇列,資料庫遷移,生成模版檔案等;你都可以透過這個指令碼來完成,這也是官方推薦的最佳實踐之一。如你可以透過:
php artisan serve
啟動本地開發環境php artisan tinker
Local Playgroundphp artisan migrate
執行資料庫遷移等
和其他框架類似,Ruby on Rails 為我們提供了 rails、Django 為我們提供了 manage.py。我覺得優秀的框架都會提供一系列的 Dev Tools 幫助開發者更好的駕馭它,更優秀的框架如 Spring 除外。
接下來我們將嘗試構建一個簡易的課程系統,在這個系統中有教師(Teacher),學生(Student)和課程(Course),它們之間覆蓋了簡單的一對一、一對多、多對多等的關係,這在日常開發中也很常見。
我會按照我理解的最佳實踐的做法,一步步實現一個完整的 CURD;但不會一來就把 Laravel 的各個優秀元件丟擲來,而是遇到什麼元件後再嘗試理解它為什麼要這樣設計、比起其他框架的優勢在哪裡。這篇文章不會包含所有的程式碼,但你仍然可以透過這個倉庫 godruoyi/laravel-best-practice 的提交記錄看到我是如何一一步構建起來的。
Make Model
我們的第一步是根據 Laravel 提供的 Artisan 命令生成對應的 Model;在實際的開發中我們通常會提供額外的引數以便生成模型的時候一起生成額外的模版檔案,如資料庫遷移檔案、測試檔案、Controller 等等;我們還將用 make:model
為 Course 生成一個 CURD Controller,相關的幾個 commit 我列在下面了,每個 Commit 我都儘量做到了最小:
- artisan make:model Teacher -msf
- artisan make:model Course -a –api –pest
- definition database fields of courses table & definition model relation
- definition course seeder
當模型及模型之間的關係定義完成後,在我看來整個開發任務就已經完成 50% 了。因為我們已經完成了資料表中欄位的定義、表與表的關係、以及最重要的一步:如何將資料及資料之間的關係寫入資料庫中,下面簡單的來介紹下在 Laravel 是如何完成的。
Database Migration
Laravel 的 Migration 提供了一套便捷的 API 方便我們完成絕大多數資料庫及表欄位的定義。Migration 的定義完整的保留了整個應用的所有遷移歷史。透過這些檔案我們可以在任何一個新的地方快速的重建我們的資料庫設計。所有資料庫的變更都透過 migration 的方式來完成也是 Laravel 推薦的最佳實踐之一。
Laravel Migration 還提供了 Rollback 機制,既可以 rollback 最近的一次資料庫變更。不過我不建議大家在生產環境這樣做;生產環境的資料庫遷移應該始終保持向前滾動,而不應該含有向後 Rollback 的操作。比如你在上一次變更操作中錯誤的設定了某個表的索引,那我理解的正確的做法不是回滾,而是建立一個新的遷移檔案,並在新的遷移檔案中 ALTER 之前的修改。
一個現代化的框架,應該有 Migration,下面是 Course 及中間表的定義:
Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('description')->nullable();
$table->unsignedBigInteger('teacher_id')->index();
$table->text('content')->nullable();
$table->timestamps();
});
// create pivot table
Schema::create('course_student', function (Blueprint $table) {
$table->unsignedBigInteger('course_id')->index();
$table->unsignedBigInteger('student_id')->index();
$table->timestamps();
$table->primary(['course_id', 'student_id']);
});
Model Relationship
Laravel 另一個強大之處在於可以透過 Eloquent 抽象「模型與模型」之間的關係;舉個例子,在下面的定義中我們描述了一個 Course 可以有多個 Student、一個 Teacher;以及一個 Student 可能有多個 Course。
// Models/Course.php
public function students(): BelongsToMany
{
return $this->belongsToMany(Student::class);
}
public function teacher(): hasOne
{
return $this->hasOne(Teacher::class);
}
一旦模型間的關係定義完成,我們就可以非常方便的透過 Laravel Eloquent 查詢它們之間的資料關係。Laravel 會自動幫我們處理複雜的 Join 操作,還能在一定條件下幫我們處理如 N+1 問題。來看一個例子:
$course = Course::with('teacher', 'students')->find(1)
// assert
expect($course)
->id->toBe(1)
->students->each->toBeInstanceOf(Student::class)
->teacher->toBeInstanceOf(Teacher::class);
這個例子中我們查詢了 ID 為 1 的課程及它所關聯的教師及學生;這將產生 3 條 SQL操作,其中還包含了一條跨中間表(course_student)的查詢,而這過程中我們不需要做任何操作,Laravel 會自動根據你 model 的定義生成對應的 Join 操作。
select * from "courses" where "id" = 1
select * from "teachers" where "teachers"."id" in (5)
select
"students".*,
"course_student"."course_id" as "pivot_course_id",
"course_student"."student_id" as "pivot_student_id"
from "students"
inner join "course_student"
on "students"."id" = "course_student"."student_id"
where "course_student"."course_id" in (1)
How to save data to database
Laravel Factory 提供了一種很好的方式來 Mock 測試資料,一旦我們定義好 Model 的 Factory 規則,我們就能輕鬆的在開發階段模擬出一個關係完整的資料。這比起我們手動為前端製造測試資料要方便和可靠得多,如下面的例子將為每一個課程分配一個教師和不確定數量的學生:
// database/seeders/CourseSeeder.php
$students = Student::all();
$teachers = Teacher::all();
Course::factory()->count(10)->make()->each(function ($course) use ($students, $teachers) {
$course->teacher()->associate($teachers->random());
$course->save();
$course->students()->attach($students->random(rand(0, 9)));
});
最後我們透過執行 php artisan migrate --seed
,Laravel 會自動同步所有的資料庫遷移檔案並按照 Laravel Factory 定義的規則生成一個關係完備的測試資料。
Laravel Route
在 Laravel 中我們還可以非常方便的管理應用的路由;Laravel 的路由是集中式路由,所有的路由全部寫在一兩個檔案中;Laravel 的 Route 給開發者暴露了一套簡單的 API,而透過這些 API 我們就能輕鬆的註冊一個符合行業標準的 RSETful 風格的路由,如我們為我們課程註冊的路由:
Route::apiResource('courses', CourseController::class);
Laravel 會自動幫我們註冊 5 條路由如下所示,包括用於新增操作的 POST 請求,用於刪除的 DELETE 請求等:
Laravel 路由雖然是非常優秀的設計,但它卻不是最高效的設計。Laravel 用一個陣列儲存你註冊過的所有路由;在進行路由匹配時,Laravel 會用你當前請求的 pathinfo 來匹配已經註冊的所有路由;當你的路由數量超級多時,最壞情況下你需要 O(n) 次才能找出匹配的路由。不過這點複雜度比起註冊路由&啟動服務的開銷幾乎可以忽略不計,並且一個應用也不會有數量過多的路由,加之 Laravel 還單獨提供了
artisnan route:cache
命令來快取路由的註冊和匹配。我猜這也是為什麼 Laravel 不需要實現其他優秀的路由演算法如 Radix Tree 的原因吧。
Create Course
接下來我們來看在 Laravel 中是如何優雅的儲存資料,這部分的記錄你可以參考下面這幾個 commit:
我們知道在進行資料操作前,都需要先對資料進行校驗。而 Laravel 提供的 FormRequest 就可以非常方便的做到這一點;你可以在 FormRequest 中定義前端傳入的每一個欄位的驗證規則。如是否必須,ID 是否應該在資料庫中存在等:
class StoreCourseRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'content' => 'nullable|string',
'teacher_id' => 'required|exists:teachers,id',
'students' => 'nullable|array',
'students.*' => 'sometimes|int|exists:students,id',
];
}
}
如果你嘗試傳入一些無效的資料,Laravel 會直接幫我們驗證並返回錯誤資訊,如下面的 teacher_id 在資料庫中並不存在。
$ echo -n '{"name": "hello", "teacher_id": 9999}' | http post http://127.0.0.1:8000/api/courses -b
{
"errors": {
"teacher_id": [
"The selected teacher id is invalid."
]
},
"message": "The selected teacher id is invalid."
}
得益於 Laravel 強大的的輔助函式和豐富的 API,在下面的程式碼中我們甚至可以做到一行程式碼就完成課程的建立及依賴關係的更新。不過這些都屬於「茴」字的幾種寫法,在真實開發中我們應該選擇適合團隊並且簡單易懂的。但我覺得正是這種最求極值的體驗讓每個用了 Laravel 的人都愛上了它。
// app/Http/Controllers/CourseController.php
public function store(StoreCourseRequest $request)
{
$course = tap(
Course::create($request->validated()),
fn ($course) => $course->students()->sync($request->students)
);
return response()->json(compact('course'), 201);
}
Storage Helper
除了上面用到的 tap 輔助函式,Laravel 另一個優秀的地方是為我們提供了超級多的輔助函式;有運算元組的 Arr,操作字串的 Str,操作集合的 Collection,操作時間的 Carbon 等。
collect(['alice@gmail.com', 'bob@yahoo.com', 'carlos@gmail.com'])
->countBy(fn ($email) => Str::of($email)->after('@')->toString())
->all(); // ['gmail.com' => 2, 'yahoo.com' => 1]
這裡還有一個有趣的現象:在 Laravel 中,輔助函式通常會放在一個名叫 Support
的檔案下面的;而這在其他框架中通常會被叫做 utils
。在我看來如果單比命名,support
在這裡要優雅得多;並且 Laravel 的原始碼中到處都充滿這這種匠人式的設計;不管是函式的命名、註釋、甚至是什麼時候該空行,都有著自己的設計思考在裡面。在 PSR2 程式碼規範中,還有專門的 Laravel 格式化風格。
寫了這麼久的程式碼,我不知道我寫的程式碼到底夠不夠好,但好在是能嗅到一點點壞程式碼的味道了,而這一切都全部得益於 Laravel。
舉個例子,你可以隨便點開一個框架的原始碼檔案(如Kernel.php),看看它的命名,看看它方法的設計。我覺得這些技能在所有語言中都是通用的。
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
Testing
Laravel 為我們提供了另一個優秀的設計就是測試。它為我們提供了種類眾多的測試,包括 HTTP 測試、瀏覽器測試(行為測試)、單元測試、資料庫測試等。作為後端開發,測試應該是所有環節中最重要的一部分;我們可以不用為每個函式都編寫單元測試,但對於暴露出去的每一個 API,都應該有足夠的 Feature 測試來覆蓋大部分可能的情況。
在 Laravel 中我們可以非常方便的為每一個 API 編寫功能測試,如下面我們為建立課程編寫的 HTTP 測試:
uses(RefreshDatabase::class);
it('create course fails if the teacher is not exist', function () {
$course = [
'name' => 'Laravel',
'description' => 'The Best Laravel Course',
'teacher_id' => 1, // teacher not exist
];
$this->postJson(route('courses.store'), $course)
->assertStatus(422)
->assertJsonValidationErrors(['teacher_id']);
});
it('create course successfully with 1 students', function () {
Teacher::factory()->create(['name' => 'Godruoyi']);
Student::factory()->create(['name' => 'Bob']);
$this->assertDatabaseCount(Teacher::class, 1);
$this->assertDatabaseCount(Student::class, 1);
$course = [
'name' => 'Laravel',
'description' => 'The Best Laravel Course',
'teacher_id' => 1,
'students' => [1],
];
$this->postJson(route('courses.store'), $course)
->assertStatus(201);
expect(Course::find(1))
->students->toHaveCount(1)
->students->first()->name->toBe('Bob')
->teacher->name->toBe('Godruoyi');
});
Update & Select & Delete
接下來我們來看如何在 Laravel 中實現查詢/刪除/更新操作,這部分的記錄你可以參考下面這幾個 Commit:
- feat: create course and related testing
- feat: show course and testing
- feat: update course and testing
- feat: delete course and testing
- feat: use laravel resources
public function index(Request $request)
{
$courses = Course::when($request->name, fn ($query, $name) => $query->where('name', 'like', "%{$name}%"))
->withCount('students')
->with('teacher')
->paginate($request->per_page ?? 10);
return new CourseCollection($course);
}
public function show(Course $course)
{
return new CourseResource($course->load('teacher', 'students:id,name'));
}
在 Laravel 中可以高效的使用 Eloquent ORM 實現各種查詢;如上面的例子中我們使用了 withCount
來查詢課程的學生數量、用 with
載入課程對應的教師;還可以指定生成的 SQL 查詢只包含某幾個欄位如 students:id,name
。我們還使用了 Laravel Resource 來格式化最終的輸出格式,這樣做的原因是很多情況下我們不希望直接將資料庫的欄位暴露出去,你甚至還能在 Laravel Resource 中按不同的角色顯示不同的欄位,如下面的 secret
欄位只有當使用者是 admin 時才返回:
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Abstract API
Laravel 另一個優雅的地方是給開發者提供了很多優秀的元件,如 Cache、Filesystem、Queue、View、Auth、Event、Notifaction 等。這些元件都用一個共通的設計:即開發者只需要面對一套高度抽象的 API 而不用關心具體的實現。舉個例子,Laravel Cache Store 的部分 API 定義如下:
interface Store
{
public function get($key);
public function put($key, $value, $seconds);
}
在使用 Cache 時,我們基本不用關心到底用的是檔案快取還是 Redis 快取;在使用佇列時也不用關心用的是 sync 佇列還是專業的 QM 如 Kafka。這在日常開發中十分有用,因為你不需要在本地配置各種複雜的服務。你可以在開發階段在 .env
檔案中將你的快取驅動改為本地磁碟,將你的佇列驅動改為本地同步佇列;當你完成所有開發後,只需要在 staging/prod 環境修改 .env
的值就可以了,你幾乎不需要做什麼額外的工作。
Laravel Core - Container
Laravel Container 是整個 Laravel 框架中最核心的部分,所有的一切都是建立在它之上的。
我們知道容器只有兩個功能:
- 裝東西(bind)
- 從容器裡取東西(get)
所有用到容器的框架其本質都是在框架啟動的時候瘋狂的往容器裡裝東西,容器裡面的東西越多,容器提供的功能越大。如 Java 的 Spring 會在編譯時為 Sprint Container 填充不同的物件,在使用時就能向容器獲取不同的值。Laravel Container 也類似;Laravel 還巧妙的提供了 Service Provider 的方式來往容器裡裝東西,它的定義如下:
interface ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
/**
* Bootstrap any application services.
*/
public function boot(): void
}
每個 Service Provider 在註冊階段都會向 container 中設定不同的值;如 CacheServiceProvider 會向容器中註冊 Cache 物件,後續在使用 Cache::get
時就使用的是這裡註冊的 Cache 物件,在註冊階段不應該向容器中獲取值,因為此時服務可能還沒有 Ready;啟動階段一般用來控制如何啟動你的服務,如你可以在這個階段中 Connect to Server、Start engin 等等。Laravel 預設會註冊 20 多個 Service Provider,每個 Service Provider 都為 Laravel 提供了一種新的能力:如 Cookie/Session/DB/Filesystem 等。它的所有的核心元件都是透過這種方式註冊的,正是因為如此眾多的 Service Provider 才使得 Laravel Container 更加強大。
我最喜歡 Laravel Container 的一點是它支援獲取任何物件,即使容器裡沒有,它也能給你造一個。Laravel Container 支援自動幫你構造容器中不存在的物件,如果這構造這個物件時還依賴另外的物件,Laravel 會嘗試遞迴的建立它,舉個例子:
class A
{
public function __construct(public B $b) {}
}
class B
{
public function __construct(public C $c) {}
}
class C
{
public function __construct(public string $name = 'Hello C')
}
$a = (new Container())->get(A::class);
expect($a)
->not->toBeNull()
->b->not->toBeNull()
->b->c->not->toBeNull()
->b->c->name->toBe('Hello C');
正是因為這個特性,所以在 Laravel 的絕大多數方法引數中,你可以隨意的注入任意數量的引數;這也是我最喜歡的一點。Laravel 會自動幫我們從容器中獲取它,如果容器不存在,則會嘗試初始化它。如我們上面的 CURD 的例子中,Request 物件就是 Laravel 自動注入的,你還可以在後面注入任意數量的引數:
class CourseController extends Controller
{
public function index(Request $request, A $a, /* arg1, arg2, ... */)
{
// ...
}
}
Laravel Pipeline
Laravel 另一個優秀的設計是 Pipeline ;Laravel 的 Pipeline 貫穿了整個框架的生命週期,可以說整個框架都是在一個流水線的管道里啟動起來的。而 Laravel Pipeline 的實現也很有趣;我們知道在常見的 Pipeline 設計中,大多會透過 for 迴圈來實現,而 Laravel 則採用的是最簡單卻又最複雜的實現 array_reduce
。
我們知道 array_reduce 可以將一組資料串起來執行,如:
array_reduce([1, 2, 3], fn($carry, $item) => $carry + $item) // 6
但當 array_reduce 遇到全是閉包的呼叫時,情況就複雜了:
$pipelines = array_reduce([Middleware1, Middleware2, /* ... */], function ($stack, $pipe) {
return return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
return $pipe(...$parameters);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
})
}, function ($passable) {
return $this->router->dispatch($passable);
})
// send request through middlewares and then get response
$response = $pipelines($request);
上面的程式碼其實是 Laravel 中介軟體的核心程式碼,也是 Laravel 啟動流程的核心實現;雖然加入了各種樣的閉包後導致函式閱讀起來十分痛苦,但它的本質其實很簡單;就是像洋蔥一樣將所有的中介軟體包起來,然後讓請求從最外層一層一層的穿過它,每一層都可以決定是否繼續向下執行,而最後的心臟部分是最終要執行的操作。舉個簡單的例子,我們可以將一段文字透過各種過濾後再儲存進資料庫,如:
(new Pipeline::class)
->send('<p>This is the HTML content of a blog post</p>')
->through([
ModerateContent::class,
RemoveScriptTags::class,
MinifyHtml::class,
])
->then(function (string $content) {
return Post::create([
'content' => $content,
...
]);
});
Laravel Comnication
Laravel 的強大離不開社群的支援,這十年來 Laravel 官方已經發布了 20 多種周邊生態,這裡摘抄一個來自@白宦成關於 Laravel 和其他框架的對比圖。
專案 | Laravel | Rails | Django |
---|---|---|---|
ORM | 有 | 有 | 有 |
資料庫遷移 | 有 | 有 | 有 |
傳送郵件 | |||
接收郵件 | 無 | 無 | |
管理框架 | 無 | ||
單頁管理 | 無 | ||
系統檢查框架 | 無 | ||
Sitemap | 無 | 無 | |
RSS & Atom | 無 | 無 | |
多站點框架 | 無 | 無 | |
前端處理 | 無 | ||
WebSocket | |||
佇列 | 無 | ||
文字編輯器 | 無 | 無 | |
GIS | 無 | 無 | |
訊號排程框架 | 無 | 無 | |
支付框架 | 無 | 無 | |
瀏覽器測試 | 無 | ||
自動化部署工具 | 無 | 無 | |
Redis 排程 | 無 | 無 | |
完整使用者系統 | 無 | 無 | |
Feature Flag | 無 | 無 | |
Code Style Fixer | 無 | 無 | |
搜尋框架 | 無 | 無 | |
OAuth | 無 | 無 | |
系統分析 | 無 | 無 |
除了官方,社群本身已有非常多的第三方擴充套件;有快速生成 Admin 管理後臺的各種 Generater、有操作 Excel 的 SpartnerNL/Laravel-Excel、有高效操作圖片的 Intervention/image、還有最近要被納入預設測試框架的 Pest 以及在屎一樣的 API 之上構建出來的最好用的微信 SDK EasyWechat。你幾乎能在 PHP 生態中找到任何你想找的輪子。
說到這兒,不得不說 PHP 生態中了一個強大的存在 Symfony。Symfony 完全是另一個可以和 Laravel 媲美的框架,甚至在很多設計上比 Laravel 還要超前;並且 Laravel 的核心元件如路由/Request/Container 都是構建在 Symfony 之上的。但 Symfony 的推廣沒有 Laravel 那麼好運,Symfony 釋出到現在已經 12 年了,仍然處於不溫不火的地位(國內看的話),我想大概是沒有一個像 Taylor Otwell 一樣即會寫程式碼還會營銷的 KOL 吧。
正是因為這些強大的社群支援幫助 Laravel 變得更加強大,也正是因為這些繁榮的生態保護著 PHP 一步一步走到現在。有些開發者可能覺得 PHP 已經走向衰亡了,並且十分鄙視 PHP 著門語言。我其實很不明白作為一名工程師為什麼我們會瞧不上某一門語言?每一門語言都有著自己天然的優勢,PHP 作為一門指令碼語言在 WEB 開發這塊兒有著極快的開發速度,加上上手難度低,工資不高,對於初創型企業來何嘗不為一個好的選擇呢。我不會因為寫 Python 就覺得 PHP 屁都不如,也不因為寫 Rust 就覺得 Go 狗都不如;在我看來,語言只是實現產品的一種方式,不同的語言在不同的領域有自己的優勢,我們應該學習不止一門語言,並儘量瞭解每一門語言的優缺點,在完成開發時選擇自己以及團隊合適的,而不是隻會寫 Java 就覺得其他語言啥都不是。
不足
Laravel 為人垢弊的問題就是太慢了,一個普通的應用一個 RTT 可能也要 100~200 ms;當遇到稍微大一點的併發請求時,CPU 的負載就奔著 90% 去了。為了解決 Laravel 速度太慢這一問題,Laravel 團隊在 2021 年的時候推出了 Laravel/Octane,如果你對 Laravel Octane 感興趣,也可以看看我之前寫的文章 — Laravel Octane 初體驗。加持了 Laravel Octane 的應用,我們可以把請求響應做到 20ms 以內。
不過我覺得 Laravel 的不足不在效能,畢竟 PHP 作為指令碼語言,就算我們把它最佳化到極致,也不可能達到類似 Go 那麼高的吞吐率,如果真的是為了效能,那為什麼不選擇其他更適合的語言呢?
在我看來最大的不足是繁重的社群生態;Laravel 之前只有 Blade 模版引擎,其語法和其他模版引擎大同小異,學起來很容易上手;後來 Laravel 推出了 Livewire 和 Inertiajs。Livewire 和 Inertiajs 都是一種類前端框架,它們提供了一種更加高效的方式來管理前端頁面,並且能更好的和 Laravel 整合在一起。但是它卻帶來了更高的學習成本和更多人力資源的浪費。本來我們只需要熟悉標準的 Vue/React API 就好了,現在卻不得不學習一種新的語法,而這些語法是構建在我們熟悉的 API 之上的;有時候你原始的 API 你知道怎麼寫,但是新框架的新語法讓你不得不檢視更多的文件甚至原始碼,你不得不花更多的時間去適配它;而當你的團隊有新人接手這些專案時,他也得跟你走一樣的路,並且 Laravel 團隊說不定哪天還會棄用它們(如 Laravel-Mix)。
這裡還有個例子是 Laravel 在之前推出了 Laravel Bootcamp 用來教新人怎麼快速上手 Laravel,但這之前只推出了兩個版本,即 Livewire 和 Inertia,好在是被社群大佬及時反應後才在再後來加上了最原始的 Blade 支援。
Laravel 官方還推出了 Laravel Sail、Laravel Herd 還有更早之前推出現在被棄用的 Laravel Homestead 等本地開發環境工具;而部署工具 Laravel 推出了 Laravel Forge、Laravel Vapor 還有 Laravel Envoyer;如果你作為一個 Laravel 新人你知道用什麼搭建本地開發環境嗎?又用什麼部署你的 Laravel 應用嗎?說實話我用了 Laravel 這麼久我也不知道。我更建議大家的是如果你對 Laravel 感興趣,不要一來就接觸 Laravel 這些複雜的概念,老老實實的在本地安裝好 PHP/Nginx/PostgreSQL 或者 Docker;而如果你要還要用它寫前端頁面,老老實實的用原生框架如 Vue/React/Bootstrap 甚至 Blade 才是更好的選擇。
Laravel 還有很優秀的設計我沒有在這篇文章中指出來,如果你對 Laravel 感興趣或者想寫出一手還不錯的程式碼,我真的建議你看一看 Laravel 的原始碼,看一看他的設計,我覺得這些設計在所有的語言中都是通用的,
原文釋出與我的部落格 godruoyi.com/, 歡迎各位前來觀摩,你也可以關注我的 X 檢視我的生活動態,enjoy。
參考
- The Laravel Framework | GitHub
- bootcamp.laravel.com/introduction
- Laravel 管道流原理 | Godruoyi Laravel China 社群
- Laravel 中介軟體原理 | Godruoyi Laravel China 社群
- NativePHP 的技術原理和實現細節 | Godruoyi
- 為什麼一個現代化的框架應該具備 Migration 功能 - 白宦成
- Laravel Octane 初體驗 | Godruoyi
- Laravel Pipelines | Martin Joo
本作品採用《CC 協議》,轉載必須註明作者和本文連結