Một Vài Gợi Ý Khi Sử Dụng Laravel 8

Laravel đã được nhiều PHP developer biết đến với việc viết mã sạch, dễ debug. Nó cũng hỗ trợ rất nhiều tính năng mà đôi khi không được liệt kê trong tài liệu hoặc có nhưng chúng đã bị xóa vì nhiều lý do khác nhau.


Mình đã làm việc với Laravel trong lĩnh vực sản xuất – sử dụng trong 4 năm và mình đã học được từ cách viết code xấu đến cách viết code tốt hơn và mình đã tận dụng những ưu điểm của Laravel kể từ lần đầu mình sử dụng nó. Mình sẽ chỉ cho bạn một vài thủ thuật có thể giúp ích cho các bạn khi bắt đầu sử dụng Laravel.

Sử Dụng Local Scopes Khi Bạn Muốn Truy Vấn Một Cái Gì Đó

Laravel có một cách hay để viết truy vấn cho trình điều khiển cơ sở dữ liệu của bạn bằng cách sử dụng Query Builder:

$orders = Order::where('status', 'delivered')->where('paid', true)->get();

Điều này khá tốt vì giúp chúng ta không cần quan tâm đến SQL mà chú trọng vào việc viết code xây dựng ứng dụng. Nhưng đoạn code này có thể được viết tốt hơn nếu chúng ta sử dụng Local Scopes.


Local Scopes cho phép chúng ta tạo các phương thức Query Builder mà chúng ta có thể xâu chuỗi khi cố gắng truy xuất dữ liệu. Ví dụ, thay vì các câu lệnh -> where (), chúng ta có thể sử dụng -> delivery () và -> pay () một cách rõ ràng hơn.

Đầu tiên, trong Order model, mình sẽ thêm các phương thức như bên dưới:

Class Order extends Model
{
   ...
   public function scopeDelivered($query) {
      return $query->where('status', 'delivered');
   }
   public function scopePaid($query) {
      return $query->where('paid', true);
   }
}

Khi khai báo Local Scopes, bạn nên đặt tên như vậy scope[Something]. Bằng cách này, Laravel sẽ biết rằng đây là một Scope và sẽ sử dụng nó trong Trình tạo truy vấn của bạn.

$orders = Order::delivered()->paid()->get();

Để truy xuất động, bạn có thể sử dụng Dynamic Local Scopes. Mỗi Scope cho phép bạn đưa vào các tham số.

class Order extends Model
{
   ...
   public function scopeStatus($query, string $status) {
      return $query->where('status', $status);
   }
}
$orders = Order::status('delivered')->paid()->get();

Ở phần sau của bài viết này, các bạn sẽ tìm hiểu lý do tại sao bạn nên sử dụng snake_case cho các trường cơ sở dữ liệu. Theo mặc định, Laravel sử dụng where [Something] để thay thế phạm vi trước đó. Vì vậy, thay như những gì đã làm ở phía trên, các bạn có thể viết như vậy:

Order::whereStatus('delivered')->paid()->get();

Laravel sẽ tìm kiếm snake_case của Something từ where[Something]. Nếu bạn có trường shipping_status trong Database, bạn có thể sử dụng như sau:

Order::whereShippingStatus('delivered')->paid()->get();

Sử Dụng Request Files Khi Cần Thiết

Các bạn có thể xác thự lỗi như thế này trong Controller:

public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => ['required', 'unique:posts', 'max:255'],
        'body' => 'required',
    ]);
    // The blog post is valid...
}

Nhưng khi bạn có quá nhiều code trong Controller có thể làm Code của bạn thêm “rối”. Bạn muốn giảm càng nhiều code càng tốt trong Controller. Ít nhất, đây là điều đầu tiên mình nghĩ nếu mình phải viết nhiều logic.


Laravel cung cấp cho bạn một cách để xác thực các yêu cầu bằng cách tạo các Class Request và sử dụng chúng thay vì Class Request mặc định. Các bạn chỉ cần tạo mới Class Request như vậy:

php artisan make:request StoreBlogPost

Truy cập theo đường dẫn app/Http/Requests/, các bạn sẽ thấy file Request vừa mới tạo:

class StoreBlogPostRequest extends FormRequest
{
   public function authorize()
   {
      return $this->user()->can('create.posts');
   }
   public function rules()
   {
       return [
           'title' => ['required', 'unique:posts', 'max:255'],
           'body' => 'required',
       ];
   }
}

Bây giờ, thay vì sử dụng Illuminate\Http\Request, các bạn nên sử dụng Class Request vừa mới tạo:

use App\Http\Requests\StoreBlogPostRequest;
public function store(StoreBlogPostRequest $request)
{
    // The blog post is valid...
}

Phương thức authorize() phải là một boolean. Nếu sai, nó sẽ throw ra 403, vì vậy hãy đảm bảo rằng bạn catch được nó trong phương thức render() của app/Exceptions/Handler.php:

public function render($request, Exception $exception)
{
   if ($exception instanceof \Illuminate\Auth\Access\AuthorizationException) {
      //
   }
return parent::render($request, $exception);
}

Phương thức bị thiếu trong lớp yêu cầu là hàm messages(), là một mảng chứa các thông báo sẽ được trả về trong trường hợp xác thực không thành công:

class StoreBlogPostRequest extends FormRequest
{
   public function authorize()
   {
      return $this->user()->can('create.posts');
   }
   public function rules()
   {
       return [
           'title' => ['required', 'unique:posts', 'max:255'],
           'body' => 'required',
       ];
   }
   public function messages()
   {
      return [
        'title.required' => 'The title is required.',
        'title.unique' => 'The post title already exists.',
        ...
      ];
   }
}

Các bạn sẽ sử dụng biến errors trong blade templates để xuất ra các lỗi:

@if ($errors->any())
   @foreach ($errors->all() as $error)
      {{ $error }}
   @endforeach
@endif

Trong trường hợp bạn muốn nhận một thông báo xác thực một trường cụ thể, bạn có thể viết như bên dưới:

<input type="text" name="title" />
@if ($errors->has('title'))
   <label class="error">{{ $errors->first('title') }}</label>
@endif

Magic Scopes

Các bạn có thể dựng các Magic Scropes có sẵn trong Laravel:

  • Truy xuất kết quả bằng create_at theo thứ tự giảm dần:
User::latest()->get();
  • Truy xuất kết quả từ bất cứ field nào theo thứ tự giảm dần:
User::latest('last_login_at')->get();
  • Truy xuất kết quả the thứ tự ngẫu nhiên:
User::inRandomOrder()->get();
  • Thực hiện truy vấn khi một điều kiện nào đó đúng:
// Let's suppose the user is on news page, and wants to sort it by newest first
// mydomain.com/news?sort=new
User::when($request->query('sort'), function ($query, $sort) {
   if ($sort == 'new') {
      return $query->latest();
   }
   
   return $query;
})->get();

Sử Dụng Relationships Để Tránh Các Truy Vấn Lớn

Bạn đã bao giờ sử dụng rất nhiều phép nối trong một truy vấn chỉ để lấy thêm thông tin chưa? Khá khó để viết các lệnh SQL đó, ngay cả với Query Builder, nhưng các model đã làm được điều đó với các Relationships. Ban đầu, các bạn sẽ không quen do lượng thông tin mà tài liệu cung cấp quá lớn, nhưng điều này sẽ giúp bạn hiểu rõ hơn về cách mọi thứ hoạt động và cách làm cho ứng dụng của bạn chạy mượt mà hơn.


Các bạn có thể tìm hiểu kĩ hơn về Relationship từ Laravel ở đây.

Sử Dụng Job Cho Các Công Việc Tốn Nhiều Thời Gian

Laravel Jobs là một công cụ mạnh mẽ cần có để chạy các tác vụ nền.

  • Bạn muốn gửi email? => Sử dụng Jobs.
  • Bạn muốn phát một tin nhắn? => Sử dụng Jobs.
  • Bạn muốn xử lý hình ảnh? => Sử dụng Jobs.

Jobs giúp bạn loại bỏ thời gian tải cho người dùng của bạn đối với các tác vụ tốn thời gian như trên. Chúng có thể được đưa vào các hàng đợi đã đặt tên, chúng có thể được ưu tiên. Laravel đã triển khai các hàng đợi ở hầu hết mọi nơi có thể: xử lý một số PHP trong nền hoặc gửi thông báo hoặc sự kiện phát sóng.

Các bạn có thể tìm hiểu kĩ hơn về Queue tại đây.

Không Lưu Trữ Dữ Liệu Tĩnh Liên Quan Đến Model Trong Configs:

Điều mình hay làm là lưu trữ những dữ liệu tĩnh liên quan đến model trong model. Thay vì viết như vậy:

BettingOdds.php

class BettingOdds extends Model
{
   ...
}

config/bettingOdds.php

return [
   'sports' => [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ],
];

Và truy cập các biến đó bằng cách sử dụng:

config('bettingOdds.sports.soccer');

Mình thường viết như vậy:

BettingOdds.php

class BettingOdds extends Model
{
   protected static $sports = [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ];
}

Rồi sử dụng chúng bằng cách:

BettingOdds::$sports['soccer'];
class BettingOdds extends Model
{
   protected static $sports = [
      'soccer' => 'sport:1',
      'tennis' => 'sport:2',
      'basketball' => 'sport:3',
      ...
   ];
public function scopeSport($query, string $sport)
   {
      if (! isset(self::$sports[$sport])) {
         return $query;
      }
      
      return $query->where('sport_id', self::$sports[$sport]);
   }
}
BettingOdds::sport('soccer')->get();

Sử Dụng Collections Thay Vì Xử Lý Mảng

Trước đây, chúng ta quen làm việc với mảng thô như vậy:

$fruits = ['apple', 'pear', 'banana', 'strawberry'];
foreach ($fruits as $fruit) {
   echo 'I have '. $fruit;
}

Bây giờ, chúng ta có thể sử dụng các phương pháp nâng cao sẽ giúp chúng ta xử lý dữ liệu trong các mảng. Chúng ta có thể lọc, biến đổi, lặp lại và sửa đổi dữ liệu bên trong một mảng:

$fruits = collect($fruits);
$fruits = $fruits->reject(function ($fruit) {
   return $fruit === 'apple';
})->toArray();
['pear', 'banana', 'strawberry']

Các bạn có thể tìm hiểu kĩ hơn về Collections ở đây.

Sử Dụng Các Packages Có Sẵn Và Tránh Viết Những Cái Mới Nếu Không Cần Thiết

Một vài Packages mà mình hay sử dụng:

Leave a Comment