laravel-query-builders

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel Query Builders

Laravel 查询构建器

Always use custom query builders instead of local scopes.
Related guides:
  • Models - Model integration with custom builders
  • Controllers - Using query objects in controllers
始终使用自定义查询构建器而非本地作用域。
相关指南:
  • Models - 模型与自定义构建器的集成
  • Controllers - 在控制器中使用查询对象

Why Custom Builders Over Scopes

为什么选择自定义构建器而非作用域

❌ Do NOT use local scopes.
✅ Use custom query builders because they provide:
  1. Better type hinting - Full IDE autocomplete
  2. Type-safe nested queries - Type-hint closures in
    whereHas()
    ,
    orWhereHas()
    , etc.
  3. Better organization - All query logic in one class
  4. More composable - Easier to chain and compose
  5. Easier testing - Test query logic in isolation
❌ 请勿使用本地作用域。
✅ 使用自定义查询构建器的原因:
  1. 更优的类型提示 - 完整的IDE自动补全
  2. 类型安全的嵌套查询 - 为
    whereHas()
    orWhereHas()
    等方法中的闭包提供类型提示
  3. 更清晰的代码组织 - 所有查询逻辑集中在一个类中
  4. 更强的可组合性 - 更易于链式调用和组合
  5. 更简单的测试 - 可独立测试查询逻辑

Basic Builder Structure

基础构建器结构

php
<?php

declare(strict_types=1);

namespace App\Builders;

use App\Enums\OrderStatus;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;

class OrderBuilder extends Builder
{
    public function wherePending(): self
    {
        return $this->where('status', OrderStatus::Pending);
    }

    public function whereCompleted(): self
    {
        return $this->where('status', OrderStatus::Completed);
    }

    public function whereCustomer(User|int $customer): self
    {
        $customerId = $customer instanceof User ? $customer->id : $customer;
        return $this->where('customer_id', $customerId);
    }

    public function whereTotalGreaterThan(int $amount): self
    {
        return $this->where('total', '>', $amount);
    }

    public function wherePlacedBetween(Carbon $start, Carbon $end): self
    {
        return $this->whereBetween('placed_at', [$start, $end]);
    }

    public function withRelated(): self
    {
        return $this->with(['customer', 'items.product', 'shipments']);
    }

    public function recent(): self
    {
        return $this->latest('placed_at');
    }
}
php
<?php

declare(strict_types=1);

namespace App\Builders;

use App\Enums\OrderStatus;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;

class OrderBuilder extends Builder
{
    public function wherePending(): self
    {
        return $this->where('status', OrderStatus::Pending);
    }

    public function whereCompleted(): self
    {
        return $this->where('status', OrderStatus::Completed);
    }

    public function whereCustomer(User|int $customer): self
    {
        $customerId = $customer instanceof User ? $customer->id : $customer;
        return $this->where('customer_id', $customerId);
    }

    public function whereTotalGreaterThan(int $amount): self
    {
        return $this->where('total', '>', $amount);
    }

    public function wherePlacedBetween(Carbon $start, Carbon $end): self
    {
        return $this->whereBetween('placed_at', [$start, $end]);
    }

    public function withRelated(): self
    {
        return $this->with(['customer', 'items.product', 'shipments']);
    }

    public function recent(): self
    {
        return $this->latest('placed_at');
    }
}

Type-Safe Nested Queries

类型安全的嵌套查询

Type-hint closures for full IDE support in relationship queries:
php
public function whereHasItems(array|string $productIds): self
{
    return $this->whereHas('items', function (OrderItemBuilder $query) use ($productIds): void {
        $query->whereIn('product_id', (array) $productIds);
    });
}
Usage:
php
Order::query()
    ->whereHas('items', function (OrderItemBuilder $query): void {
        $query->whereActive()        // Custom method - autocomplete works!
              ->whereProduct($id);   // Full type safety!
    })
    ->whereHas('customer', function (CustomerBuilder $query): void {
        $query->whereVerified()      // Custom method
              ->wherePremium();      // IDE knows all methods!
    })
    ->get();
为闭包添加类型提示,在关联查询中获得完整的IDE支持:
php
public function whereHasItems(array|string $productIds): self
{
    return $this->whereHas('items', function (OrderItemBuilder $query) use ($productIds): void {
        $query->whereIn('product_id', (array) $productIds);
    });
}
使用示例:
php
Order::query()
    ->whereHas('items', function (OrderItemBuilder $query): void {
        $query->whereActive()        // 自定义方法 - 自动补全生效!
              ->whereProduct($id);   // 完整的类型安全!
    })
    ->whereHas('customer', function (CustomerBuilder $query): void {
        $query->whereVerified()      // 自定义方法
              ->wherePremium();      // IDE可识别所有方法!
    })
    ->get();

PHPDoc for External Methods

为外部方法添加PHPDoc注释

Document methods from Spatie packages or macros:
php
/**
 * @method static OrderBuilder whereState(string $column, string|array $state)
 * @method static OrderBuilder whereNotState(string $column, string|array $state)
 */
class OrderBuilder extends Builder
{
    // ...
}
为Spatie包或宏定义的方法添加文档:
php
/**
 * @method static OrderBuilder whereState(string $column, string|array $state)
 * @method static OrderBuilder whereNotState(string $column, string|array $state)
 */
class OrderBuilder extends Builder
{
    // ...
}

Builder Traits

构建器特质

Extract reusable query logic:
php
<?php

declare(strict_types=1);

namespace App\Builders\Concerns;

use Illuminate\Support\Arr;

trait HasProducts
{
    public function whereHasProducts(array|string $productIds): self
    {
        return $this->whereHas('products', function ($query) use ($productIds): void {
            $query->whereIn('id', Arr::wrap($productIds));
        });
    }

    public function whereHasActiveProducts(): self
    {
        return $this->whereHas('products', function ($query): void {
            $query->where('active', true);
        });
    }
}
Usage in builder:
php
class OrderBuilder extends Builder
{
    use HasProducts;

    // ...
}
提取可复用的查询逻辑:
php
<?php

declare(strict_types=1);

namespace App\Builders\Concerns;

use Illuminate\Support\Arr;

trait HasProducts
{
    public function whereHasProducts(array|string $productIds): self
    {
        return $this->whereHas('products', function ($query) use ($productIds): void {
            $query->whereIn('id', Arr::wrap($productIds));
        });
    }

    public function whereHasActiveProducts(): self
    {
        return $this->whereHas('products', function ($query): void {
            $query->where('active', true);
        });
    }
}
在构建器中使用:
php
class OrderBuilder extends Builder
{
    use HasProducts;

    // ...
}

Register Builder in Model

在模型中注册构建器

Preferred: PHP Attribute (Laravel 11+)

推荐方式:PHP属性(Laravel 11+)

php
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;

#[UseEloquentBuilder(OrderBuilder::class)]
class Order extends Model
{
    // ...
}
php
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;

#[UseEloquentBuilder(OrderBuilder::class)]
class Order extends Model
{
    // ...
}

Alternative: Static Property

替代方式:静态属性

php
class Order extends Model
{
    protected static string $eloquentBuilder = OrderBuilder::class;

    // ...
}
php
class Order extends Model
{
    protected static string $eloquentBuilder = OrderBuilder::class;

    // ...
}

Deprecated: Method Override

已废弃:重写方法

php
// ❌ Don't use this approach anymore
public function newEloquentBuilder($query): OrderBuilder
{
    return new OrderBuilder($query);
}
php
// ❌ 请勿再使用此方式
public function newEloquentBuilder($query): OrderBuilder
{
    return new OrderBuilder($query);
}

Usage Examples

使用示例

Basic Chaining

基础链式调用

php
Order::query()
    ->wherePending()
    ->whereTotalGreaterThan(10000)
    ->wherePlacedBetween(now()->subWeek(), now())
    ->withRelated()
    ->recent()
    ->paginate();
php
Order::query()
    ->wherePending()
    ->whereTotalGreaterThan(10000)
    ->wherePlacedBetween(now()->subWeek(), now())
    ->withRelated()
    ->recent()
    ->paginate();

Lazy Iteration

惰性迭代

php
Order::query()
    ->whereCompleted()
    ->lazyById()
    ->each(function (Order $order): void {
        // Process order
    });
php
Order::query()
    ->whereCompleted()
    ->lazyById()
    ->each(function (Order $order): void {
        // 处理订单
    });

Complex Queries

复杂查询

php
$orders = Order::query()
    ->wherePending()
    ->whereCustomer($user)
    ->whereHasItems([$productId1, $productId2])
    ->wherePlacedBetween($startDate, $endDate)
    ->withRelated()
    ->get();
php
$orders = Order::query()
    ->wherePending()
    ->whereCustomer($user)
    ->whereHasItems([$productId1, $productId2])
    ->wherePlacedBetween($startDate, $endDate)
    ->withRelated()
    ->get();

Empty Builders

空构建器

Always create builders even if empty initially - for future extensibility:
php
<?php

declare(strict_types=1);

namespace App\Builders;

use Illuminate\Database\Eloquent\Builder;

class CustomerBuilder extends Builder
{
    // Empty for now, but ready for future methods
}
即使初始为空,也要创建构建器 - 便于未来扩展:
php
<?php

declare(strict_types=1);

namespace App\Builders;

use Illuminate\Database\Eloquent\Builder;

class CustomerBuilder extends Builder
{
    // 目前为空,但已为未来方法做好准备
}

Common Builder Methods

常见构建器方法

Status Filtering

状态筛选

php
public function whereActive(): self
{
    return $this->where('status', 'active');
}

public function whereInactive(): self
{
    return $this->where('status', 'inactive');
}
php
public function whereActive(): self
{
    return $this->where('status', 'active');
}

public function whereInactive(): self
{
    return $this->where('status', 'inactive');
}

Date Ranges

日期范围

php
public function whereCreatedAfter(Carbon $date): self
{
    return $this->where('created_at', '>', $date);
}

public function whereCreatedToday(): self
{
    return $this->whereDate('created_at', today());
}
php
public function whereCreatedAfter(Carbon $date): self
{
    return $this->where('created_at', '>', $date);
}

public function whereCreatedToday(): self
{
    return $this->whereDate('created_at', today());
}

User/Owner Filtering

用户/所有者筛选

php
public function whereUser(User|int $user): self
{
    $userId = $user instanceof User ? $user->id : $user;
    return $this->where('user_id', $userId);
}
php
public function whereUser(User|int $user): self
{
    $userId = $user instanceof User ? $user->id : $user;
    return $this->where('user_id', $userId);
}

Relationship Loading

关联加载

php
public function withFullRelations(): self
{
    return $this->with([
        'user',
        'items.product',
        'customer.address',
    ]);
}
php
public function withFullRelations(): self
{
    return $this->with([
        'user',
        'items.product',
        'customer.address',
    ]);
}

Builder Organization

构建器组织方式

app/Builders/
├── OrderBuilder.php
├── CustomerBuilder.php
├── ProductBuilder.php
└── Concerns/
    ├── HasProducts.php
    ├── HasDates.php
    └── HasStatus.php
app/Builders/
├── OrderBuilder.php
├── CustomerBuilder.php
├── ProductBuilder.php
└── Concerns/
    ├── HasProducts.php
    ├── HasDates.php
    └── HasStatus.php

Summary

总结

Custom query builders:
  • Provide better IDE support than scopes
  • Enable type-safe nested queries
  • Keep query logic organized
  • Are easier to test
  • Support method chaining
Never use local scopes - always use custom builders.
自定义查询构建器:
  • 比作用域提供更优的IDE支持
  • 支持类型安全的嵌套查询
  • 使查询逻辑更有条理
  • 更易于测试
  • 支持方法链式调用
永远不要使用本地作用域 - 始终使用自定义构建器。