Lưu ý: Tôi thương xuyên update tài liệu về review source code Laravel tại đây: https://github.com/minhbangchu/laravel-best-practices
1. Mỗi Class, Mỗi Method chỉ nên có 1 trách nhiệm duy nhất.
Bad:
(Bạn có thể tham khảo thêm ở nguyên lý số 1: "Single responsibility principle" của SOLID )
2. Hãy để Model béo tốt còn Controller thì đẹp dáng ^^
Đặt tất cả các xử lý logic liên quan đến DB vào các Model vì đó mới là chức năng của Model và Controller chỉ gọi đến phương thức logic đó trong Model thôi,
Bad:
3. Hãy thực hiện Validation trong lớp Request.
Chức năng của Request là nơi kiểm soát tính hợp lệ của dữ liệu đầu vào. Sau khi một request được gửi lên từ phía client thì request sẽ được validate dữ liệu bởi class form request trước khi được controller sử dụng. Vì vậy xin hãy tiếp tục giữ dáng đẹp cho Controller.
Bad:
4. Các xử lý Business logic nên nằm trong lớp Service
Controller không nên kiêm nhiệm thêm các xử lý Logic vì vậy hãy tách chúng ra vào lớp Service. Việc này sẽ giảm tải cho controller, dễ dàng làm Unit test, dễ dàng sử dụng lại các business logic đó ở nơi khác.
Bad:
5. Đừng lặp lại code. Hãy sử dụng lại khi có thể.
Điều này cũng áp dụng cho Blade templates và Eloquent scopes
Bad:
6. Ưu tiên Eloquent hơn là sử dụng Query Builder và raw SQL queries. Ưu tiên collections hơn là sử dụng arrays
Eloquent giúp cho bạn viết SQL đẹp, dễ đọc và bảo trì code hơn. Eloquent cũng cung cấp sẵn cho bạn nhiều phương thức tuyệt vời như: Soft deletes, Events, Scopes ... Tương tự Collections là một đối tượng hỗ trợ nhiều phương thức linh động và hay sử dụng hơn là Array.
Bad:
7. Tự động gán các tham số của một HTTP request vào các biến hoặc đối tượng (Mass assignment)
Bad:
8. Không chạy các câu Query trong Blade template. Xử lý Vấn đề N + 1 bằng Eager Loading
Bad:
Đoạn code Bad phía trên có 2 vấn đề:
- Không nên viết các truy vấn dữ liệu trong Blade template.
- Nếu có 100 User, sẽ có 101 câu Query sẽ được chạy.
Nếu sử dụng đoạn code Good bên dưới, chúng ra sẽ sử dụng công cụ Eager Loading của Laravel. Với việc sử dụng hàm with() hay load(), ta có thể load một lúc ra tất cả các User cùng với profile name chỉ bằng một câu truy vấn SQL. Và 101 câu query vào DB ở trên sẽ được tối ưu lại còn 2 câu Query.
Laravel cung cấp cho chúng ta một hệ thống Eager Loading rất hoàn hảo và mạnh mẽ. Bạn có thể tham khảo thêm ở đây eager-loading
9. Hãy comment cho code của bạn, nhưng nếu có thể hãy sử dụng tên phương thức và biến dễ hiểu để thay thế cho việc Comment.
Bad:
10. Không nên đặt JS và CSS trong Blade template và không đặt HTML nào trong các lớp PHP
Bad:
11. Sử dụng file config, language, constant thay thế cho Text trong code.
Bad:
12. Sử dụng cú pháp ngắn hơn và dễ đọc hơn nếu có thể
Bad:
13. Sử dụng IoC container hoặc facades thay vì tạo Class mới
Việc khởi tạo một Class mới sẽ tạo ra sự liên kết phực tạp giữa các lớp, đồng thời sẽ phức tạp hơn trong quá trình testing. Vì vậy hãy sử dụng IoC Contrainer. Điều này giúp tuân thủ nguyên lý "Dependency Inversion Principle" của SOLID.
Bad:
14. Không lấy trực tiếp dữ liệu từ file .env
File .env sẽ không được sử dụng trong môi trường Production vì vậy hãy đữa dữ liệu và file config sau đó sử dụng helper config() helper để sử dụng dữ liệu đó.
Bad:
15. Lưu trữ ngày theo định dạng chuẩn. Sử dụng Accessors và Mutators để sửa đổi định dạng ngày.
Accessors và Mutators cho phép bạn format các attributes của Eloquent khi lấy ra từ một model hoặc cũng có thể set giá trị cho chúng. Tại sao phải làm vậy? Vì trong project của bạn, 1 thuộc tính có thể sẽ được sử dụng ở rất nhiều nơi, chẳng lẽ mỗi lần sử dụng bạn lại phải format nó.
Bad:
16. Hãy tuân theo Quy ước đặt tên của Laravel
Laravel sử dụng PSR standards. Bạn có thể tham khảo tại https://www.php-fig.org/psr/psr-2/
Ngoài ra nên sử dụng các quy tắc sau vì đang được áp dụng rộng rãi trong cộng đồng Laravel:
(Sẽ tiếp tục update)
--------
Tham khảo:
- https://laravel.com/
- https://www.php-fig.org/psr/psr-2/
- https://github.com/alexeymezenin/laravel-best-practices#contents
Bad:
public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}
Good:
public function getFullNameAttribute()
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerifiedClient()
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong()
{
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}
2. Hãy để Model béo tốt còn Controller thì đẹp dáng ^^
Đặt tất cả các xử lý logic liên quan đến DB vào các Model vì đó mới là chức năng của Model và Controller chỉ gọi đến phương thức logic đó trong Model thôi,
Bad:
public function index() { $clients = Client::verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); return view('index', ['clients' => $clients]); }Good:
public function index() { return view('index', ['clients' => $this->client->getWithNewOrders()]); } class Client extends Model { public function getWithNewOrders() { return $this->verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); } }
3. Hãy thực hiện Validation trong lớp Request.
Chức năng của Request là nơi kiểm soát tính hợp lệ của dữ liệu đầu vào. Sau khi một request được gửi lên từ phía client thì request sẽ được validate dữ liệu bởi class form request trước khi được controller sử dụng. Vì vậy xin hãy tiếp tục giữ dáng đẹp cho Controller.
Bad:
public function store(Request $request) { $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required', 'publish_at' => 'nullable|date', ]); .... }Good:
public function store(PostRequest $request) { .... } class PostRequest extends Request { public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', 'publish_at' => 'nullable|date', ]; } }
4. Các xử lý Business logic nên nằm trong lớp Service
Controller không nên kiêm nhiệm thêm các xử lý Logic vì vậy hãy tách chúng ra vào lớp Service. Việc này sẽ giảm tải cho controller, dễ dàng làm Unit test, dễ dàng sử dụng lại các business logic đó ở nơi khác.
Bad:
public function store(Request $request) { if ($request->hasFile('image')) { $request->file('image')->move(public_path('images') . 'temp'); } .... }Good:
public function store(Request $request) { $this->articleService->handleUploadedImage($request->file('image')); .... } class ArticleService { public function handleUploadedImage($image) { if (!is_null($image)) { $image->move(public_path('images') . 'temp'); } } }
5. Đừng lặp lại code. Hãy sử dụng lại khi có thể.
Điều này cũng áp dụng cho Blade templates và Eloquent scopes
Bad:
public function getActive() { return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); } public function getArticles() { return $this->whereHas('user', function ($q) { $q->where('verified', 1)->whereNotNull('deleted_at'); })->get(); }Good:
public function scopeActive($q) { return $q->where('verified', 1)->whereNotNull('deleted_at'); } public function getActive() { return $this->active()->get(); } public function getArticles() { return $this->whereHas('user', function ($q) { $q->active(); })->get(); }
6. Ưu tiên Eloquent hơn là sử dụng Query Builder và raw SQL queries. Ưu tiên collections hơn là sử dụng arrays
Eloquent giúp cho bạn viết SQL đẹp, dễ đọc và bảo trì code hơn. Eloquent cũng cung cấp sẵn cho bạn nhiều phương thức tuyệt vời như: Soft deletes, Events, Scopes ... Tương tự Collections là một đối tượng hỗ trợ nhiều phương thức linh động và hay sử dụng hơn là Array.
Bad:
SELECT * FROM `articles` WHERE EXISTS (SELECT * FROM `users` WHERE `articles`.`user_id` = `users`.`id` AND EXISTS (SELECT * FROM `profiles` WHERE `profiles`.`user_id` = `users`.`id`) AND `users`.`deleted_at` IS NULL) AND `verified` = '1' AND `active` = '1' ORDER BY `created_at` DESCGood:
Article::has('user.profile')->verified()->latest()->get();
7. Tự động gán các tham số của một HTTP request vào các biến hoặc đối tượng (Mass assignment)
Bad:
$article = new Article; $article->title = $request->title; $article->content = $request->content; $article->verified = $request->verified; // Add category to article $article->category_id = $category->id; $article->save();Good:
$category->article()->create($request->all());
8. Không chạy các câu Query trong Blade template. Xử lý Vấn đề N + 1 bằng Eager Loading
Bad:
@foreach (User::all() as $user) {{ $user->profile->name }} @endforeachGood:
// Eager Loading $users = User::with('profile')->get(); ... @foreach ($users as $user) {{ $user->profile->name }} @endforeach
Đoạn code Bad phía trên có 2 vấn đề:
- Không nên viết các truy vấn dữ liệu trong Blade template.
- Nếu có 100 User, sẽ có 101 câu Query sẽ được chạy.
Nếu sử dụng đoạn code Good bên dưới, chúng ra sẽ sử dụng công cụ Eager Loading của Laravel. Với việc sử dụng hàm with() hay load(), ta có thể load một lúc ra tất cả các User cùng với profile name chỉ bằng một câu truy vấn SQL. Và 101 câu query vào DB ở trên sẽ được tối ưu lại còn 2 câu Query.
Laravel cung cấp cho chúng ta một hệ thống Eager Loading rất hoàn hảo và mạnh mẽ. Bạn có thể tham khảo thêm ở đây eager-loading
9. Hãy comment cho code của bạn, nhưng nếu có thể hãy sử dụng tên phương thức và biến dễ hiểu để thay thế cho việc Comment.
Bad:
if (count((array) $builder->getQuery()->joins) > 0)
Better:// Determine if there are any joins. if (count((array) $builder->getQuery()->joins) > 0)Good:
if ($this->hasJoins())
10. Không nên đặt JS và CSS trong Blade template và không đặt HTML nào trong các lớp PHP
Bad:
let article = `{{ json_encode($article) }}`;
Good:<input id="article" type="hidden" value="{{ json_encode($article) }}"> Or <button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>Code JS được đặt trong một file Javascript
let article = $('#article').val();
11. Sử dụng file config, language, constant thay thế cho Text trong code.
Bad:
public function isNormal() { return $article->type === 'normal'; } return back()->with('message', 'Your article has been added!');Good:
public function isNormal() { return $article->type === Article::TYPE_NORMAL; } return back()->with('message', __('app.article_added'));
12. Sử dụng cú pháp ngắn hơn và dễ đọc hơn nếu có thể
Bad:
$request->session()->get('cart'); $request->input('name');Good:
session('cart'); $request->name;Một số ví dụ khác về cú pháp ngắn hơn trong Laravel các bạn có thể tham khảo:
Common syntax | Shorter and more readable syntax |
---|---|
Session::get('cart') | session('cart') |
$request->session()->get('cart') | session('cart') |
Session::put('cart', $data) | session(['cart' => $data]) |
$request->input('name'), Request::get('name') | $request->name, request('name') |
return Redirect::back() | return back() |
is_null($object->relation) ? $object->relation->id : null } | optional($object->relation)->id |
return view('index')->with('title', $title)->with('client', $client) | return view('index', compact('title', 'client')) |
$request->has('value') ? $request->value : 'default'; | $request->get('value', 'default') |
Carbon::now(), Carbon::today() | now(), today() |
App::make('Class') | app('Class') |
->where('column', '=', 1) | ->where('column', 1) |
->orderBy('created_at', 'desc') | ->latest() |
->orderBy('age', 'desc') | ->latest('age') |
->orderBy('created_at', 'asc') | ->oldest() |
->select('id', 'name')->get() | ->get(['id', 'name']) |
->first()->name | ->value('name') |
Việc khởi tạo một Class mới sẽ tạo ra sự liên kết phực tạp giữa các lớp, đồng thời sẽ phức tạp hơn trong quá trình testing. Vì vậy hãy sử dụng IoC Contrainer. Điều này giúp tuân thủ nguyên lý "Dependency Inversion Principle" của SOLID.
Bad:
$user = new User; $user->create($request->all());Good:
public function __construct(User $user) { $this->user = $user; } .... $this->user->create($request->all());
14. Không lấy trực tiếp dữ liệu từ file .env
File .env sẽ không được sử dụng trong môi trường Production vì vậy hãy đữa dữ liệu và file config sau đó sử dụng helper config() helper để sử dụng dữ liệu đó.
Bad:
$apiKey = env('API_KEY');
Good:// config/api.php 'key' => env('API_KEY'), // Use the data $apiKey = config('api.key');
15. Lưu trữ ngày theo định dạng chuẩn. Sử dụng Accessors và Mutators để sửa đổi định dạng ngày.
Accessors và Mutators cho phép bạn format các attributes của Eloquent khi lấy ra từ một model hoặc cũng có thể set giá trị cho chúng. Tại sao phải làm vậy? Vì trong project của bạn, 1 thuộc tính có thể sẽ được sử dụng ở rất nhiều nơi, chẳng lẽ mỗi lần sử dụng bạn lại phải format nó.
Bad:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}Good
// Model protected $dates = ['ordered_at', 'created_at', 'updated_at'] public function getSomeDateAttribute($date) { return $date->format('m-d'); } // View {{ $object->ordered_at->toDateString() }} {{ $object->ordered_at->some_date }}Bạn tham khảo thêm về Accessor và Mutator tại đây nhé: https://laravel.com/docs/5.6/eloquent-mutators#accessors-and-mutators
16. Hãy tuân theo Quy ước đặt tên của Laravel
Laravel sử dụng PSR standards. Bạn có thể tham khảo tại https://www.php-fig.org/psr/psr-2/
Ngoài ra nên sử dụng các quy tắc sau vì đang được áp dụng rộng rãi trong cộng đồng Laravel:
What | How | Good | Bad |
---|---|---|---|
Controller | singular | ArticleController | |
Route | plural | articles/1 | |
Named route | snake_case with dot notation | users.show_active | |
Model | singular | User | |
hasOne or belongsTo relationship | singular | articleComment | |
All other relationships | plural | articleComments | |
Table | plural | article_comments | |
Pivot table | singular model names in alphabetical order | article_user | |
Table column | snake_case without model name | meta_title | |
Model property | snake_case | $model->created_at | |
Foreign key | singular model name with _id suffix | article_id | |
Primary key | - | id | |
Migration | - | 2017_01_01_000000_create_articles_table | |
Method | camelCase | getAll | |
Method in resource controller | table | store | |
Method in test class | camelCase | testGuestCannotSeeArticle | |
Variable | camelCase | $articlesWithAuthor | |
Collection | descriptive, plural | $activeUsers = User::active()->get() | |
Object | descriptive, singular | $activeUser = User::active()->first() | |
Config and language files index | snake_case | articles_enabled | |
View | snake_case | show_filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contract (interface) | adjective or noun | Authenticatable | |
Trait | adjective | Notifiable |
--------
Tham khảo:
- https://laravel.com/
- https://www.php-fig.org/psr/psr-2/
- https://github.com/alexeymezenin/laravel-best-practices#contents
Tuyệt vời, cảm ơn bạn
Trả lờiXóa