Php 如何使用Laravel中的多对多关系检索嵌套类别的产品并对结果排序?

Php 如何使用Laravel中的多对多关系检索嵌套类别的产品并对结果排序?,php,laravel,categories,voyager,Php,Laravel,Categories,Voyager,我想检索所选类别以及子类别的产品,甚至可能需要根据价格或日期进行订购。请让我知道应该对控制器进行哪些更改 我对两个表使用了多对多关系:Categories和Products,以及category_product来关联这两个表 示例 Products: id, name, price, created_at (并非所有类别都有子类别) 礼物 书 玩具 男孩 姑娘们 电话 三星 诺基亚 如果用户单击手机,则应显示“手机”、“三星”或“诺基亚”类别的所有产品 数据库 Products: id

我想检索所选类别以及子类别的产品,甚至可能需要根据价格或日期进行订购。请让我知道应该对控制器进行哪些更改

我对两个表使用了多对多关系:Categories和Products,以及category_product来关联这两个表

示例

Products: id, name, price, created_at
(并非所有类别都有子类别)

礼物

玩具

  • 男孩

  • 姑娘们

电话

  • 三星

  • 诺基亚

如果用户单击手机,则应显示“手机”、“三星”或“诺基亚”类别的所有产品

数据库

Products: id, name, price, created_at
代码:

类别模型:

class Category extends Model
{
    public function products()
    {
        return $this->belongsToMany('App\Product');
    }

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

    public function children()
    {
        return $this->hasMany('App\Category', 'parent_id');
    }
}
public function nestedStructure()
{
   return $this->children()->with([
         'nestedStructure',
         'products' => function($q) {
             $q->orderBy('created_at', 'desc');
         }
   );
}
产品模型

class Product extends Model
{
    public function categories()
    {
        return $this->belongsToMany('App\Category');
    }
}
看法


{{csrf_field()}}
排序方式:
最新的
价格由低到高
价格由高到低
@foreach($category->products as$product)
image)}}“alt=“{{$product->name}}”>
{{$product->name}
${{$product->price}
@endforeach

我正在使用Laravel 6.2版和Voyager 1.3版。

如果您的类别深度不受限制,则需要递归关系

我认为这样的方法可以奏效:

在您的类别模型中:

class Category extends Model
{
    public function products()
    {
        return $this->belongsToMany('App\Product');
    }

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

    public function children()
    {
        return $this->hasMany('App\Category', 'parent_id');
    }
}
public function nestedStructure()
{
   return $this->children()->with([
         'nestedStructure',
         'products' => function($q) {
             $q->orderBy('created_at', 'desc');
         }
   );
}

好的,所以首先你需要修改你的分类模型,让所有的孩子和父母在一起

类别模型

class Category extends Model
{
    public function products()
    {
        return $this->belongsToMany('App\Product');
    }

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

    public function children()
    {
        return $this->hasMany('App\Category', 'parent_id');
    }

    // recursive, loads all descendants
    public function recursiveChildren()
    {
       return $this->children()->with('recursiveChildren');
    }
}
现在,根据给定的多对多关系,一个产品可能属于多个类别,但我们不希望同一个产品一再出现。因此,您的控制器可能的修复方法是

class ProductController extends Controller {

    public function index($slug, Request $request)
    {

        $categories = Category::where('slug', $slug)->with('recursiveChildren')->whereNull('parent')->get();

        if($categories->count() == 0) abort(404, 'Page not found');

        $products = collect([]); // its a helper method to make collections

        foreach($categories as $category) {
            $category_products = $category->products;
            foreach($category_products as $product) {
               if(!$products->contains($product)) $products->push($product);
            }
        }

        if($request->input('soryBy')) {
            switch ($request->sortBy)
            {
                case 'latest':
                    $products = $products->sortBy('created_at');
                    break;
                case 'asc':
                    $products = $products->sortBy('price');
                    break;
                case 'desc':
                    $products = $products->sortByDesc('price');
                    break;
                default:
                    $products;
                    break;
            }
        }

        return view('products', compact('products'));
    }
}
现在让我们稍微修改一下视图

<form id="sortProducts" class="form-inline" method="get">
   {{ csrf_field() }}
   <label for="sortBy">Sort By:</label>
   <select name="sortBy" id="sortBy">
      <option value="latest">Latest</option>
      <option value="asc">Price Low to Hight</option>
      <option value="desc">Price High to Low</option>
   </select>
</form>

@foreach($products as $product)
    <div class="product">
        <img border="0" src="{{Voyager::image($product->image)}}" alt="{{$product->name}}">
        <div class="product-name">{{$product->name}}</div>
        <div class="product-price">${{$product->price}}</div>
    </div>
@endforeach

{{csrf_field()}}
排序方式:
最新的
价格由低到高
价格由高到低
@foreach($products as$product)
image)}}“alt=“{{$product->name}}”>
{{$product->name}
${{$product->price}
@endforeach

至少有两种解决方案

溶液1(纯Laravel): 将这两种方法添加到您的
类别中
型号:

public function descendants()
{
    $collection = new \Illuminate\Support\Collection();

    foreach ($this->children as $child) {
        $collection->add($child);
        $collection = $collection->merge($child->descendants());
    }

    return $collection;
}

public function getRouteKeyName()
{
    return 'slug';
}
并在您的
产品控制器中使用它,如下所示:

class ProductController extends Controller
{
    public function index(Request $request, Category $category)
    {
        $categories = $category->descendants()->add($category)->pluck('id');

        $products = DB::table('category_product AS cp')
            ->join('products', 'cp.product_id', '=', 'products.id')
            ->select('products.*')
            ->whereIn('cp.category_id', $categories)
            ->get();

        return view('products', compact('category', 'products'));
    }
}
然后可以在视图文件中输出它们:

@forelse($products as $product)
    <div class="product">
        <img border="0" src="{{ Voyager::image($product->image) }}" alt="{{ $product->name }}">
        <div class="product-name">{{ $product->name }}</div>
        <div class="product-price">${{ $product->price }}</div>
    </div>
@empty
    <div class="product">
        There are no products in this category.
    </div>
@endforelse
$table->nestedSet()替换
parent_id
在您的
类别
表和相关迁移文件中:

Schema::create('categories', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('slug')->unique();
    $table->nestedSet();
    $table->timestamps();
});
然后,更新您的
类别
型号,如下所示:

use Illuminate\Database\Eloquent\Model;
use Kalnoy\Nestedset\NodeTrait;

class Category extends Model
{
    use NodeTrait;

    protected $fillable = [
        'name',
        'slug',
    ];

    public function products()
    {
        return $this->belongsToMany('App\Product');
    }

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

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

    public function getRouteKeyName()
    {
        return 'slug';
    }
}
现在,您可以在控制器中使用它:

class ProductController extends Controller
{
    public function index(Request $request, Category $category)
    {
        $categories = Category::descendantsAndSelf($category->id)->pluck('id');

        $products = DB::table('category_product AS cp')
            ->join('products', 'cp.product_id', '=', 'products.id')
            ->select('products.*')
            ->whereIn('cp.category_id', $categories)
            ->get();

        return view('products', compact('category', 'products'));
    }
}
您可以按解决方案1所示进行输出


请注意,我假设您在路线定义中使用
{category}
键。(见)例如:

Route::get('/products/category/{category}', 'ProductController@index');

阅读第一个以创建、更新和删除嵌套集合(类别)中的项目。

如果选择第一个类别,例如
$category->children->first()->products()
@Joseph,您可以检索它。您能再解释一下吗?不是所有类别都有子类!请看这篇文章:我尝试了您提供的解决方案,但不幸的是,无法检索父类别的产品!对于子类别,子类别链接显示404错误。在我注释了abort(404,“找不到页面”)
之后,即使对于子类别,也没有检索到产品。因此,我删除了
whereNull('parent_id')
,解决了这个问题,但是父类别仍然没有显示产品!!!
class ProductController extends Controller
{
    public function index(Request $request, Category $category)
    {
        $categories = Category::descendantsAndSelf($category->id)->pluck('id');

        $products = DB::table('category_product AS cp')
            ->join('products', 'cp.product_id', '=', 'products.id')
            ->select('products.*')
            ->whereIn('cp.category_id', $categories)
            ->get();

        return view('products', compact('category', 'products'));
    }
}
Route::get('/products/category/{category}', 'ProductController@index');