You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Laravel中定义并预加载自定义Eloquent关联——Client与PaymentMethod模型的多条件一对多关联实现

自定义Laravel Eloquent一对多关联(含"all"匹配)及预加载实现

嘿,这个需求挺实用的——既要关联常规的client_id匹配记录,还要把client_id"all"的支付方式也归到每个客户名下对吧?我来给你一步步实现这个自定义Eloquent关联,还能完美支持预加载:

1. 创建自定义关联类

Laravel默认的HasMany关联只支持单一外键匹配,所以我们需要自定义一个关联类来扩展它,实现OR条件的逻辑。

在你的项目里新建app/Relations/HasManyIncludingAll.php文件,代码如下:

<?php

namespace App\Relations;

use Illuminate\Database\Eloquent\Relations\HasMany;

class HasManyIncludingAll extends HasMany
{
    /**
     * 为单个模型的关联查询添加约束
     */
    public function addConstraints()
    {
        if (static::$constraints) {
            $this->query->where(function ($query) {
                $query->where($this->foreignKey, '=', $this->getParentKey())
                      ->orWhere($this->foreignKey, '=', 'all');
            });
        }
    }

    /**
     * 为预加载的批量查询添加约束
     */
    public function addEagerConstraints(array $models)
    {
        $parentKeys = $this->getKeys($models, $this->localKey);

        $this->query->where(function ($query) use ($parentKeys) {
            $query->whereIn($this->foreignKey, $parentKeys)
                  ->orWhere($this->foreignKey, '=', 'all');
        });
    }

    /**
     * 预加载时匹配结果到父模型(关键逻辑)
     */
    public function match(array $models, \Illuminate\Database\Eloquent\Collection $results, $relation)
    {
        $dictionary = $this->buildDictionary($results);

        // 遍历每个客户,合并自己的支付方式和"all"的支付方式
        foreach ($models as $model) {
            $key = $model->getAttribute($this->localKey);

            $related = collect()
                ->merge($dictionary[$key] ?? [])
                ->merge($dictionary['all'] ?? []);

            $model->setRelation($relation, $this->related->newCollection($related));
        }

        return $models;
    }
}

这个类的核心是重写了三个方法:

  • addConstraints:处理单个客户的关联查询,添加client_id=当前ID OR client_id="all"的条件
  • addEagerConstraints:处理批量预加载时的查询,同样添加OR条件
  • match:预加载时把每个客户自己的支付方式和"all"的支付方式合并到一起

2. 在Client模型中使用自定义关联

接下来在Client模型里引入这个关联类,定义关联方法:

<?php

namespace App\Models;

use App\Relations\HasManyIncludingAll;
use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    /**
     * 获取客户的所有支付方式(含client_id为"all"的)
     */
    public function paymentMethods()
    {
        return $this->newHasManyIncludingAll(
            $this->newQueryForRelation($this->paymentMethod()),
            $this,
            $this->paymentMethod()->getForeignKeyName(),
            $this->getKeyName()
        );
    }

    /**
     * 创建自定义关联实例的封装方法
     */
    protected function newHasManyIncludingAll($query, $parent, $foreignKey, $localKey)
    {
        return new HasManyIncludingAll($query, $parent, $foreignKey, $localKey);
    }

    /**
     * 获取PaymentMethod模型实例(用于获取外键等信息)
     */
    protected function paymentMethod()
    {
        return $this->newRelatedInstance(PaymentMethod::class);
    }
}

3. 测试使用及预加载

现在你就可以像使用普通Eloquent关联一样使用这个自定义关联了:

单个客户查询

$client = Client::find(1);
$paymentMethods = $client->paymentMethods; 
// 返回client_id=1和client_id="all"的所有支付方式

批量预加载

$clients = Client::with('paymentMethods')->get();
// 每个client的paymentMethods属性都会包含自己的和"all"的支付方式

额外注意事项

  • 确保PaymentMethod表的client_id字段类型是可以存储字符串的(比如VARCHAR),因为要存"all"这个值
  • 如果需要对关联结果排序,可以在paymentMethods方法里链式调用orderBy
public function paymentMethods()
{
    return $this->newHasManyIncludingAll(...)
        ->orderBy('created_at', 'desc');
}

内容的提问来源于stack exchange,提问作者Mouad ZIANI

火山引擎 最新活动