Bạn đã code đẹp, chuẩn?

Đăng bởi:

Lưu Minh Tuân

Đăng ngày:

Dec 30, 2020

Đăng ở:

Công cụ, tiện ích, thủ thuật

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 ArticlesController
Route plural articles/1 article/1
Named route snake_case with dot notation users.show_active users.show-active, show-active-users
Model singular User Users
hasOne or belongsTo relationship singular articleComment articleComments, article_comment
All other relationships plural articleComments articleComment, article_comments
Table plural article_comments article_comment, articleComments
Pivot table singular model names in alphabetical order article_user user_article, articles_users
Table column snake_case without model name meta_title MetaTitle; article_meta_title
Model property snake_case $model->created_at $model->createdAt
Foreign key singular model name with _id suffix article_id ArticleId, id_article, articles_id
Primary key - id custom_id
Migration - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
Method camelCase getAll get_all
Method in resource controller table store saveArticle
Method in test class camelCase testGuestCannotSeeArticle test_guest_cannot_see_article
Variable camelCase $articlesWithAuthor $articles_with_author
Collection descriptive, plural $activeUsers = User::active()->get() $active, $data
Object descriptive, singular $activeUser = User::active()->first() $users, $obj
Config and language files index snake_case articles_enabled ArticlesEnabled; articles-enabled
View snake_case show_filtered.blade.php showFiltered.blade.php, show-filtered.blade.php
Config snake_case google_calendar.php googleCalendar.php, google-calendar.php
Contract (interface) adjective or noun Authenticatable AuthenticationInterface, IAuthentication
Trait adjective Notifiable NotificationTrait

-- 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ì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 *

Repository deleted Your repository has remove
Loading