Tìm Hiểu Laravel Coding Convention

Single Responsibility Principle – Một Nhiệm Vụ Duy Nhất

Một lớp hay phương thức chỉ nên thực hiện một nhiệm vụ duy nhất

Không Nên:

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;
    }
}

Nên Code như vậy:

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;
}

Fat Models, Skinny Controllers – Code vào Models, Giảm Thiểu Controller

Đặt tất cả các logic liên quan đến Database vào các Eloquent Models hoặc Respository Class.

Không Nên:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

Nên viết như vậy:

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();
    }
}

Validation – Kiểm Tra Lỗi

Nên kiểm tra lỗi bằng cách tạo class Request, thay vì kiểm tra lỗi trong Controller.

Không Nên:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}

Nên viết như vậy:

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',
        ];
    }
}

Business Logic Should Be in Service Class – Business Login nên viết trong Service Class

Một Controller chỉ chịu trách nhiệm làm một việc gì đó, vì vậy hãy viết những code liên quan đến logic business vào Service Class.

Không Nên:

public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
    
    ....
}

Nên viết như vậy:

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');
        }
    }
}

Don’t Repeat Yourself (DRY) – Tránh Lặp Code

Tái sử dụng code nếu có thể. SRP giúp bạn tránh lặp code. Thêm vào đó, tái sử dụng blade templates, sử dụng Eloquent scopes,…

Không Nên:

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();
}

Nên viết như vậy:

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();
}

Prefer to Use Eloquent Over Using Query Builder and Raw SQL Queries. Prefer Collections Over Arrays – Sử Dụng Eloquent Thay Vì Query Builder Hay RAW SQL Query

Eloquent cho phép bạn viết code dễ đọc và dễ bảo trì. Ngoài ra, Eloquent có các công cụ tích hợp tuyệt vời như softdelete, events, scopes,…

Không Nên:

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

Nên viết như vậy cho ngắn gọn:

Article::has('user.profile')->verified()->latest()->get();

Mass assignment

Không Nên:

$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();

Nên làm như vậy:

$category->article()->create($request->validated());

Do Not Execute Queries in Blade Templates and Use Eager Loading (N + 1 Problem)

Không Nên (Nếu có 100 users, 101 DB queries sẽ thực hiện)

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

Nên viết như vậy (Nếu có 100 users. 2 DB queries sẽ thực hiện)

$users = User::with('profile')->get();

...

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach

Comment Your Code, but Prefer Descriptive Method and Variable Names Over Comments – Comment Code Đúng Cách

Không Nên:

if (count((array) $builder->getQuery()->joins) > 0)

Tốt hơn một chút:

// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)

Hoặc là như vậy cho ngắn gọn:

if ($this->hasJoins())

Do Not Put Js and CSS in Blade Templates and Do Not Put Any HTML in PHP Classes – Không Nên Viết JS và CSS Vào Blade Templates Và Không Nên Viết HTML vào PHP Class

Không Nên:

let article = `{{ json_encode($article) }}`;

Nên viết như vậy:

<input id="article" type="hidden" value='@json($article)'>

Or

<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>

Trong Javascript file:

let article = $('#article').val();

Use Config and Language Files, Constants Instead of Text in the Code – Sử Dụng Cấu Hình Và Tệp Ngôn Ngữ, Hằng Số Thay Vì Nhập Text Vào Code

Không Nên:

public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Your article has been added!');

Nên viết như vậy:

public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));

Use Standard Laravel Tools Accepted by Community – Sử Dụng Các Công Cụ Chuẩn Laravel Được Cộng Đồng Chấp Nhận

Ưu tiên dụng các gói cộng đồng và chức năng Laravel tích hợp sẵn thay vì sử dụng các gói và công cụ của bên thứ ba. Bất kỳ nhà phát triển nào sẽ làm việc với ứng dụng của bạn trong tương lai sẽ cần tìm hiểu các công cụ mới. Ngoài ra, cơ hội nhận được trợ giúp từ cộng đồng Laravel thấp hơn đáng kể khi bạn đang sử dụng gói hoặc công cụ của bên thứ ba.

TaskStandard tools3rd party tools
AuthorizationPoliciesEntrust, Sentinel and other packages
Compiling assetsLaravel MixGrunt, Gulp, 3rd party packages
Development EnvironmentLaravel Sail, HomesteadDocker
DeploymentLaravel ForgeDeployer and other solutions
Unit testingPHPUnit, MockeryPhpspec
Browser testingLaravel DuskCodeception
DBEloquentSQL, Doctrine
TemplatesBladeTwig
Working with dataLaravel collectionsArrays
Form validationRequest classes3rd party packages, validation in controller
AuthenticationBuilt-in3rd party packages, your own solution
API authenticationLaravel Passport, Laravel Sanctum3rd party JWT and OAuth packages
Creating APIBuilt-inDingo API and similar packages
Working with DB structureMigrationsWorking with DB structure directly
LocalizationBuilt-in3rd party packages
Realtime user interfacesLaravel Echo, Pusher3rd party packages and working with WebSockets directly
Generating testing dataSeeder classes, Model Factories, FakerCreating testing data manually
Task schedulingLaravel Task SchedulerScripts and 3rd party packages
DBMySQL, PostgreSQL, SQLite, SQL ServerMongoDB

Follow Laravel Naming Conventions – Quy Ước Đặt Tên

Follow theo PSR Standards. Ngoài ra, hãy tuân theo các quy ước đặt tên được chấp nhận bởi cộng đồng Laravel:

WhatHowGoodBad
ControllersingularArticleControllerArticlesController
Routepluralarticles/1article/1
Named routesnake_case with dot notationusers.show_activeusers.show-active, show-active-users
ModelsingularUserUsers
hasOne or belongsTo relationshipsingulararticleCommentarticleComments, article_comment
All other relationshipspluralarticleCommentsarticleComment, article_comments
Tablepluralarticle_commentsarticle_comment, articleComments
Pivot tablesingular model names in alphabetical orderarticle_useruser_article, articles_users
Table columnsnake_case without model namemeta_titleMetaTitle; article_meta_title
Model propertysnake_case$model->created_at$model->createdAt
Foreign keysingular model name with _id suffixarticle_idArticleId, id_article, articles_id
Primary keyidcustom_id
Migration2017_01_01_000000_create_articles_table2017_01_01_000000_articles
MethodcamelCasegetAllget_all
Method in resource controllertablestoresaveArticle
Method in test classcamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
VariablecamelCase$articlesWithAuthor$articles_with_author
Collectiondescriptive, plural$activeUsers = User::active()->get()$active, $data
Objectdescriptive, singular$activeUser = User::active()->first()$users, $obj
Config and language files indexsnake_casearticles_enabledArticlesEnabled; articles-enabled
Viewkebab-caseshow-filtered.blade.phpshowFiltered.blade.php, show_filtered.blade.php
Configsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Contract (interface)adjective or nounAuthenticationInterfaceAuthenticatable, IAuthentication
TraitadjectiveNotifiableNotificationTrait

Use Shorter and More Readable Syntax Where Possible – Sử Dụng Cú Pháp Ngắn Gọn Và Dễ Đọc Nếu Có Thể

Không Nên:

$request->session()->get('cart');
$request->input('name');

Nên:

session('cart');
$request->name;

Một vài ví dụ khác:

Common syntaxShorter 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) ? null : $object->relation->idoptional($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')

Use IoC Container or Facades Instead of New Class – Sử Dụng IoC Container Hoặc Facades Thay Vì Tạo Mới Class

Không Nên:

$user = new User;
$user->create($request->validated());

Nên:

public function __construct(User $user)
{
    $this->user = $user;
}

....

$this->user->create($request->validated());

Do Not Get Data From the .env File Directly – Không Lấy Dữ Liệu Trực Tiếp Từ File .env

Thay vào đó, hãy chuyển dữ liệu vào file cấu hình và sau đó sử dụng hàm trợ giúp config () để sử dụng dữ liệu trong ứng dụng.

Không Nên:

$apiKey = env('API_KEY');

Nên:

// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');

Store Dates in the Standard Format. Use Accessors and Mutators to Modify Date Format – Lưu Trữ Ngày Ở Định Dạng Chuẩn

Không Nê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') }}

Nên làm như vậy:

// 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 }}

Một Vài Phương Pháp Khác

  • Không viết logic code trong routes files.
  • Giảm thiểu sử dụng vanilla PHP trong blade templates.

Nguồn: https://github.com/alexeymezenin/laravel-best-practices

Leave a Comment