Appearance
Custom Query Builder
Giới thiệu
Custom Query Builder là một lớp mở rộng của Illuminate\Database\Eloquent\Builder trong Laravel, cho phép bạn thêm các phương thức truy vấn tùy chỉnh vào các model của mình. Điều này giúp đóng gói logic truy vấn phức tạp, làm cho code dễ đọc, dễ bảo trì và tái sử dụng.
Vì sao nên dùng Custom Query Builder?
- Tái sử dụng: Gom các logic truy vấn phức tạp vào một nơi để gọi lại ở nhiều nơi khác.
- Dễ đọc: Thay vì một chuỗi các điều kiện
wheredài, bạn có thể gọi một phương thức có tên rõ ràng nhưProduct::query()->hasNoBom(). - Dễ bảo trì: Logic truy vấn được tập trung tại một lớp duy nhất, giúp việc sửa đổi và mở rộng trở nên đơn giản.
Cách triển khai
Việc triển khai bao gồm 2 bước chính: tạo lớp Builder và đăng ký nó vào trong Model.
1. Tạo lớp Custom Query Builder
Tạo một lớp mới kế thừa từ Illuminate\Database\Eloquent\Builder. Đây là nơi bạn sẽ định nghĩa các phương thức truy vấn tùy chỉnh.
Ví dụ với ProductBuilder:
php
<?php
declare(strict_types=1);
namespace Modules\Masterdata\Product\Infrastructure\Builders;
use App\Builders\IWithStandardQuery;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Modules\Masterdata\Product\Infrastructure\Models\Product;
// ...
/**
* @method static ProductBuilder query()
*
* @extends Builder<Product>
*/
final class ProductBuilder extends Builder implements IWithStandardQuery
{
public function haveSpecs(): self
{
return $this->whereHas('spec');
}
public function withCategory(): self
{
return $this->with('productCategory');
}
public function hasBom(): self
{
return $this->whereHas('boms');
}
public function hasNoBom(): self
{
return $this->whereDoesntHave('boms');
}
public function standardQuery(?Request $request = null): self
{
$request = $request ?? request();
// ... (logic lọc phức tạp từ request)
if ($request->boolean('has_no_bom')) {
$this->hasNoBom();
}
// ...
return $this;
}
}2. Đăng ký Builder trong Model
Trong model tương ứng (ví dụ: Product), ghi đè phương thức newEloquentBuilder() để trả về một instance của custom builder bạn vừa tạo.
php
<?php
declare(strict_types=1);
namespace Modules\Masterdata\Product\Infrastructure\Models;
use Illuminate\Database\Eloquent\Model;
use Modules\Masterdata\Product\Infrastructure\Builders\ProductBuilder;
/**
* @method static ProductBuilder query()
*/
final class Product extends Model
{
public function newEloquentBuilder($query): ProductBuilder
{
return new ProductBuilder($query);
}
// ...
}3. Thêm PHPDoc vào Model
Để IDE (như PhpStorm) có thể hiểu và cung cấp gợi ý code (type-hinting) chính xác khi bạn gọi Product::query(), hãy thêm một PHPDoc block vào model với annotation @method.
php
/**
* @method static ProductBuilder query()
*/
final class Product extends Model
{
// ...
}Cách sử dụng
Sau khi đã đăng ký, bạn có thể sử dụng các phương thức tùy chỉnh của mình trực tiếp từ model.
php
use Modules\Masterdata\Product\Infrastructure\Models\Product;
use Illuminate\Http\Request;
// Trong một Action hoặc Service
public function findProductsWithoutBom(Request $request)
{
// Gọi phương thức tùy chỉnh `hasNoBom()` từ ProductBuilder
$products = Product::query()
->hasNoBom()
->withCategory()
->get();
// Hoặc áp dụng bộ lọc chuẩn từ request
$filteredProducts = Product::query()
->standardQuery($request)
->get();
return $products;
}Bằng cách này, logic truy vấn được đóng gói gọn gàng trong ProductBuilder, giúp code ở lớp Action/Service trở nên sạch sẽ và dễ hiểu hơn rất nhiều.