Как получить продукты из вложенной категории, используя отношение «многие ко многим» в Laravel, и упорядочить результат? - PullRequest
0 голосов
/ 28 февраля 2020

Я хочу получить товары выбранной категории, а также подкатегорий, и мне может даже потребоваться заказать их по цене или дате. Пожалуйста, дайте мне знать, какие изменения должны быть внесены в контроллер!

Я использую отношение «многие ко многим» для двух таблиц: «Категории» и «Продукты» и «category_product» для связи обоих.

Пример

(Не во всех категориях есть подкатегории)

Подарки

Книги

Игрушки

  • Мальчики

  • Девочки

Телефон

  • Samsung

  • Nokia

Если пользователь нажимает кнопку «Телефон», должны появиться все продукты категории «Телефон», «Samsung» или «Nokia»!

База данных

Products: id, name, price, created_at
Categories: id, name, slug, parent_id, sorting_order, created_at
category_product: id, category_id, product_id

Код:

Категория Модель:

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');
    }
}

Модель продукта

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

    public function index($slug, Request $request)
    {
        if ( ! isset($_GET['sortBy']))
        {
            $category = Category::where('slug', $slug)->with('products')->first();

            if ( ! $category)
            {
                abort(404, 'Page not found');
            }
        }
        else
        {
            $slug = $request->segments()[1];
            $products = Category::where('slug', $slug);
            switch ($request->sortBy)
            {
                case 'latest':
                    $category = $products->with(['products' => function ($q) {
                        $q->orderBy('created_at', 'desc');
                    }])->first();
                    break;
                case 'asc':
                    $category = $products->with(['products' => function ($q) {
                        $q->orderBy('price', 'asc');
                    }])->first();
                    break;
                case 'desc':
                    $category = $products->with(['products' => function ($q) {
                        $q->orderBy('price', 'desc');
                    }])->first();
                    break;
                default:
                    $category = $products->with('products')->first();
                    break;
            }
        }

        return view('products', compact('category'));
    }
}

Просмотр

<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($category->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

Я использую Laravel Версия 6.2 вместе с Voyager Версия 1.3.

Ответы [ 3 ]

0 голосов
/ 29 февраля 2020

Хорошо, так что сначала вам нужно изменить модель категории, чтобы все дети вместе с родителями

Модель категории

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
0 голосов
/ 15 марта 2020

Существует как минимум два решения.

Решение 1 (Pure Laravel):

Добавьте эти два метода в модель Category:

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';
}

И используйте его в вашем ProductController контроллере следующим образом:

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

Решение 2 (с использованием пакета ):

Прежде всего установите пакет:

composer require kalnoy/nestedset

Замените parent_id столбец на $table->nestedSet(); in Ваша таблица categories и связанный файл миграции:

Schema::create('categories', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('slug')->unique();
    $table->nestedSet();
    $table->timestamps();
});

Затем обновите модель Category следующим образом:

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');

Сначала прочитайте документацию , чтобы создать, обновить и удалить элемент во вложенном наборе (категориях).

0 голосов
/ 28 февраля 2020

Если глубина вашей категории не ограничена, вам понадобятся рекурсивные отношения.

Я думаю, что-то подобное может работать:

В вашей категории Модель:

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