Laravel从父类别及其子类别中获取所有产品

时间:2020-01-18 10:35:23

标签: php laravel eloquent nested

我正在尝试从一个类别及其所有子类别中检索产品。

这是我的categories桌子:

| id    | parent_id     | name          |
|----   |-----------    |-------------  |
| 1     | NULL          | Electronics   |
| 2     | 1             | Computers     |
| 3     | 2             | Accessories   |
| 4     | 3             | Keyboards     |

这是我的产品表:

| id    | category_id   | name          |
|----   |-------------  |-----------    |
| 1     | 2             | Product 1     |
| 2     | 3             | Product 2     |
| 3     | 4             | Product 3     |

假设我处于Computers类别页面中,我想显示此表及其所有子项中的产品。

因此它应该首先从ComputersAccessories以及Keyboards获得产品。

这是我的类别模型:

public function parent() {
    return $this->belongsTo(Category::class, 'parent_id');
}

public function childs() {
    return $this->hasMany(Category::class, 'parent_id');
}

public function products() {
    return $this->hasManyThrough(Product::class, Category::class, 'parent_id', 'category_id', 'id');
}

产品型号:

public function categories() {
    return $this->belongsTo(Category::class, 'category_id');
}

查询

Category::with(['products', 'childs.products'])->where('id', $category->id)->get();

返回

{
    "id":11,
    "parent_id":4,
    "name":"Computers",
    "products":[
        {
            "id":2,
            "category_id":12,
            "title":"Product 1",
            "laravel_through_key":11
        }
    ],
    "childs":[
        {
            "id":12,
            "parent_id":11,
            "name":"Accessories",
            "products":[
                {
                    "id":1,
                    "category_id":13,
                    "user_id":1,
                    "title":"Product 2",
                    "laravel_through_key":12
                }
            ]
        }
    ]
}

上面,它转义了最后一个子类别Keyboards

我尝试使用hasManyThrough关系,但是我只从ComputersAccessories获得产品,但没有达到Keyboards

因此,如果我属于某个类别,则希望从该类别树中获取所有产品。即使子类别有子类别。

我该如何实现?

谢谢。

更新

我在 Foued MOUSSI 的答案中应用了代码段:

public function childrenRecursive() {
    return $this->childs()->with('childrenRecursive');
}

$categoryIds = Category::with('childrenRecursive')->where('id', $category->id)->get();

返回

[
    {
        "id":2,
        "parent_id":1,
        "name":"Computers",
        "children_recursive":[
            {
                "id":3,
                "parent_id":2,
                "name":"Accessories",
                "children_recursive":[
                    {
                        "id":4,
                        "parent_id":3,
                        "name":"Keyboards",
                        "children_recursive":[]
                    }
                ]
            }
        ]
    }
]

我得到了类别及其所有子类别的数组,但是要从所有这些类别中获取产品,我需要使用childrenRecursive从列表中提取ID来调用类似的内容:

Product::whereIn('category_id', $categoryIds)->get();

有什么主意吗?

5 个答案:

答案 0 :(得分:2)

您可以通过以下方式修复它:

建立递归关系:(请参阅Alex Harris的答案here

// recursive, loads all descendants
// App\Category
public function childrenRecursive()
{
   return $this->childs()->with('childrenRecursive');
}

$data = Category::with(['products', 'childrenRecursive', 'childrenRecursive.products'])->where('id', 2)->get()->toArray();

#Edit:提取产品列表

在控制器内定义Flatten laravel recursive relationship collection (tree collections)函数

public function flatten($array)
{
        $flatArray = [];

        if (!is_array($array)) {
            $array = (array)$array;
        }

        foreach($array as $key => $value) {
            if (is_array($value) || is_object($value)) {
                $flatArray = array_merge($flatArray, $this->flatten($value));
            } else {
                $flatArray[0][$key] = $value;
            }
        }

        return $flatArray;
}

然后为了只拥有产品项

$data = Category::with(['products', 'childrenRecursive', 'childrenRecursive.products'])->where('id', 2)->get()->toArray();

$flatten = $this->flatten($data);

foreach ($flatten as $key => $fl) {
    // eliminate categories from $flatten array
    if (!array_key_exists('category_id', $fl)) {
        unset($flatten[$key]);
    }
}

$product = array_values($flatten);

答案 1 :(得分:2)

一种选择是使用类似laravel-adjacency-list的名称。这将允许您使用CTE来递归加载关系。

以下是进行设置的步骤(在撰写本文时)

  1. 运行composer require staudenmeir/laravel-adjacency-list:"^1.0"
  2. HasRecursiveRelationships特性添加到您的Category模型中:

    use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
    
    class Category extends Model
    {
        use HasRecursiveRelationships;
    
        ...
    }
    
  3. 将查询更改为:

    Category::with('descendants.products')->where('id', $id)->first(); //$id being the id of the parent category you want to get.
    

如果您只想获取类别中/类别下的产品,则可以执行以下操作:

Product::whereHas('category', function ($query) use ($category) {
    $query->whereIn('categories.id', $category->descendantsAndSelf()->select('id')->getQuery());
})->get();

有关如何使用laravel-adjacency-list的更多信息,请参阅documentation

答案 2 :(得分:1)

由于我使用的是 MySQL 5,如果有人对此感兴趣,我会在我的项目中使用这种方式来解决此问题。这对我来说更有意义。

如果您使用的是 MySQL 8,您可以将此问题作为参考来编辑此代码。 How to create a MySQL hierarchical recursive query?

public function scopeWhereCategoryId($query, $category_id){
    return $query->where(function($query) use ($category_id){
        $query->where('category_id', trim($category_id));
        $query->orWhereRaw("category_id in (
            select  id
            from    (select * from pm_categories
                    order by category_id, id) categories_sorted,
                    (select @pv := '" . trim($category_id) . "') initialisation
            where   find_in_set(category_id, @pv)
            and     length(@pv := concat(@pv, ',', id))
        )");
    });
}

答案 3 :(得分:1)

如果你使用 ORM Eloquent 关系

public function categoriesProduct(ProductCategory $category)
{
     $categories = ProductCategory::where('parent_id', $category->id)
                                  ->orWhere('id', $category->id)
                                  ->latest()
                                  ->get();
     return view('categoryProduct', compact('categories', 'category'));
}

答案 4 :(得分:0)

$categories  = Category::whereParentId(null)->with('children.children.children')->get();

在视图中,您可以显示带有foreach循环的项目