Đăng bởi:
Lưu Minh TuânĐăng ngày:
Dec 30, 2020Đăng ở:
Thủ Thuật Thiết KếKhi ta mới bắt đầu code, việc ta quan tâm là code làm sao chạy được 😆. Theo thời gian bạn sẽ tiến lên level cao hơn, dự án lớn hơn bạn không thể code 1 mình mà phải code theo team. Khi làm team, code không chỉ chạy được là được mà bạn phải code làm sao cho người khác đọc được, dễ hiểu và không mất quá nhiều thời gian để đọc những đoạn code bạn viết ra. Hôm nay lang thang trên mạng, mình tổng hợp được một số thủ thuật. Hi vọng sau khi đọc bài này các bạn sẽ có một bộ souce Laravel đẹp, đúng chuẩn dễ dàng bảo trì và tối ưu nhất 🤤
1. Mỗi Class, Mỗi Method chỉ nên có 1 trách nhiệm duy nhất.
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;
}
(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 logic liên quan đến DB vào các Eloquent model hoặc vào các lớp Repository nếu bạn đang sử dụng Query Builder hoặc raw Query.
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.
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)
{
....
}
// chạy lệnh: php artisan make:request PostRequest
class PostRequest extends Request
{
public function authorize()
{
true;
}
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` DESC
Good:
Article::has('user.profile')->verified()->latest()->get();
(quá ngon phải không nhỉ :D)
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());
nếu trong $request có 1 số biến không dùng đến thì bạn sử dụng $request->only(...) nhé
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 }}
@endforeach
Good:
// 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:
Or {{ $article->name }}
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') |
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:
public function store(Request $request)
{
$user = new User;
$user->create($request->all());
}
Good:
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function store(Request $request)
{
$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:
<pclass="language-markup">$apiKey = env('API_KEY');
Good:
<pclass="language-markup">// 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:
// lưu vào db định dạng cá nhân mong muốn
{{ 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
// trong Eloquent Model
protected $dates = ['ordered_at', 'created_at', 'updated_at']
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// hiển thị ra ngoài 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/8.x/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 |
-- Nguồn tham khảo
https://www.php-fig.org/psr/psr-2/
https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan
Bạn đã luyện được đến tầng thứ mấy trong bộ tuyệt kỹ chân kinh này rồi còn mình viết vậy nhưng thực hiện chỉ đếm trên đầu ngón tay thôi 😆😆😆, hãy để lại ý kiến ở dưới phần comment nhé 🤤🤤🤤🤤
Bài viết liên quan
Bình luận
Để lại bình luận
Email và số điện thoại sẽ không được công khai. Những trường bắt buộc được đánh dấu *