如何使用多个外键Laravel 5 Eloquent查询数据透视表

Dav*_*son 5 php laravel laravel-5

我有以下表结构:

| products | product_options | product_option_values | product_option_to_product |
+----------+-----------------+-----------------------+---------------------------+
| id       | id              | id                    | id                        |
|          |                 |                       | product_id                |
|          |                 |                       | product_option_id         |
|          |                 |                       | product_option_value_id   |
Run Code Online (Sandbox Code Playgroud)

我的关系看起来像这样:

// Product.php
public function options()
{
    return $this->belongsToMany('App\ProductOption', 'product_option_to_product', 'product_id', 'product_option_id')->withPivot('product_option_value_id', 'product_id');
}

// ProductOption.php
public function values()
{
    return $this->belongsToMany('App\ProductOptionValue', 'product_option_to_product', 'product_option_id', 'product_option_value_id')->withPivot('product_id');
}
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试这个:

$products = Product::with('options')->with('options.values')->first();
Run Code Online (Sandbox Code Playgroud)

要么

$products = Product::with('options.values')->first();
Run Code Online (Sandbox Code Playgroud)

我得到所有选项可能的值,因为它不使用产品ID来加载选项值.

有谁知道我怎么能确保它只加载属于该产品的值?

我可以在产品上直接与选项值建立关系,但我确实需要将它们附加到它们所属的选项上.

编辑:

我尝试了这样的连接:

$products = Product::with(['options.values' => function($q) {
    $q->join('products', 'products.id', '=', 'product_option_to_product.product_id');
}])->first();
Run Code Online (Sandbox Code Playgroud)

但它给出了相同的结果.

我现在已经实现了一个非常讨厌的修复,在我看来我执行了一些检查:

@if($value->pivot->product_id == $product->id)
    <div class="checkbox">
        <label>
            <input type="checkbox" name="{{ $option->id }}" value="{{ $value->id }}"> {{ $value->language->name }}
        </label>
    </div>
@endif
Run Code Online (Sandbox Code Playgroud)

但是,这对我来说不合适.

Gus*_*ube 3

您的问题在于表中的记录product_options不知道您正在检索的产品。

根据您在问题中提到的数据库结构,我编写了以下几行:

$product = App\Product::with('options')->with('options.values')->first();
foreach ($product->options as $option) {
    foreach ($option->values as $value) {
        printf(
            "Product: %s, Option: %d, Value: %d\n",
            $product->id,
            $option->id,
            $value->id
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码只是打印特定产品的选项和值的可能组合。我正在使用您在应用程序中创建的相同关系。

当我加载该代码时,Eloquent(Laravel 的 ORM)在数据库中运行以下查询:

-- Retrieves the first product
select * from `products` limit 1;

-- Retrieves all options using the product_id as condition
select `product_options`.*, `product_option_to_product`.`product_id` as `pivot_product_id`, `product_option_to_product`.`product_option_id` as `pivot_product_option_id`, `product_option_to_product`.`product_option_value_id` as `pivot_product_option_value_id` 
from `product_options` inner join `product_option_to_product` on `product_options`.`id` = `product_option_to_product`.`product_option_id` 
where `product_option_to_product`.`product_id` in ('1')

-- Retrieves all values using the option_id as condition
select `product_option_values`.*, `product_option_to_product`.`product_option_id` as `pivot_product_option_id`, `product_option_to_product`.`product_option_value_id` as `pivot_product_option_value_id`, `product_option_to_product`.`product_id` as `pivot_product_id` 
from `product_option_values` inner join `product_option_to_product` on `product_option_values`.`id` = `product_option_to_product`.`product_option_value_id` 
where `product_option_to_product`.`product_option_id` in ('1', '2')
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,最后一个查询没有使用product_id作为检索值的条件,因为product_options表不保存对products表的引用。

最后,我取得了和你一样的结果。尽管我正在搜索产品,但所有值都与选项相关。

解决此问题的最简单方法是在调用关系时添加查询约束。像这样:

$product = App\Product::with('options')->with('options.values')->first();
foreach ($product->options as $option) {
    foreach ($option->values()->where('product_id', $product->id)->get() as $value) {
        printf("PRO: %s, OPT: %d, VAL: %d<br>\n", $product->id, $option->id, $value->id);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,$option->values我没有使用属性来调用,而是使用了$option->values()作为方法,而不是作为属性进行调用。调用关系方法时,您可以像使用查询生成器一样使用它。

如果您不想使用此解决方法,您可能需要更改数据库结构和模型类。

--

编辑

另一种解决方案是使用查询生成器来获取特定产品的所有值。

您可以在控制器中使用以下几行来完成此操作:

$values = ProductOptionValue::select([
    'product_option_values.*',
    'product_option_to_product.option_id',
])
    ->join('product_option_to_product', 'product_option_to_product.product_option_value_id', '=', 'product_option_values.id')
    ->where('product_id', $product->id)
    ->get();
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以获得特定产品(由其给出$product->id)及其各自的所有值option_id

要在模板中使用上述查询的结果,您只需对复选框名称进行少量更改:

<div class="checkbox">
    <label>
        <input type="checkbox" name="{{ $value->option_id }}" value="{{ $value->id }}"> {{ $value->language->name }}
    </label>
</div>
Run Code Online (Sandbox Code Playgroud)