Laravel多对多关联下嵌套分类商品查询及排序控制器改造
Hey there! Let's get this sorted out so clicking a category shows products from that category and all its subcategories, plus working sorting functionality. Here's what you need to change:
Step 1: Add a Method to Get All Related Category IDs
First, let's add a static method to your Category model that fetches the ID of the selected category plus all its nested subcategories. This iterative approach is more efficient than recursive calls for larger category trees:
// app/Category.php class Category extends Model { // ... existing methods ... public static function getAllRelatedCategoryIds($slug) { // Get the main category (throws 404 if not found) $mainCategory = self::where('slug', $slug)->firstOrFail(); $categoryIds = [$mainCategory->id]; $childIds = self::where('parent_id', $mainCategory->id)->pluck('id')->toArray(); // Iterate to get all nested subcategories while (!empty($childIds)) { $categoryIds = array_merge($categoryIds, $childIds); $childIds = self::whereIn('parent_id', $childIds)->pluck('id')->toArray(); } return $categoryIds; } }
Step 2: Rewrite the Product Controller's Index Method
Your current controller only fetches products from the single selected category. Let's refactor it to use our new category ID method, clean up the sorting logic, and fetch all relevant products:
// app/Http/Controllers/ProductController.php class ProductController extends Controller { public function index($slug, Request $request) { // Get all IDs for the category and its subcategories $categoryIds = Category::getAllRelatedCategoryIds($slug); // Start building the product query $productsQuery = Product::whereHas('categories', function ($query) use ($categoryIds) { $query->whereIn('categories.id', $categoryIds); }); // Apply sorting (default to "latest" if no sortBy parameter is present) $sortOption = $request->get('sortBy', 'latest'); switch ($sortOption) { case 'asc': $productsQuery->orderBy('price', 'asc'); break; case 'desc': $productsQuery->orderBy('price', 'desc'); break; case 'latest': default: $productsQuery->orderBy('created_at', 'desc'); break; } // Fetch the products (use paginate() instead of get() if you want pagination) $products = $productsQuery->get(); // Get the main category for your view (for breadcrumbs, title, etc.) $mainCategory = Category::where('slug', $slug)->firstOrFail(); return view('products', compact('mainCategory', 'products')); } }
Key Improvements Here:
- Cleaner logic: We removed the messy
if/elseblock around the sort parameter and handle everything in one flow. - Subcategory inclusion: The
whereHasclause checks if a product belongs to any of the relevant categories (main + subcategories). - Automatic 404 handling:
firstOrFail()will throw a 404 error if the category slug doesn't exist, so you don't need manual checks.
Step 3: Update Your View
Since we're now passing a direct $products collection to the view (instead of relying on $category->products), you'll need to adjust the loop and tweak the sort dropdown:
<!-- resources/views/products.blade.php --> <!-- Remove the csrf_field() - it's not needed for GET requests! --> <form id="sortProducts" class="form-inline" method="get"> <label for="sortBy">Sort By:</label> <select name="sortBy" id="sortBy"> <option value="latest" {{ request()->sortBy == 'latest' ? 'selected' : '' }}>Latest</option> <option value="asc" {{ request()->sortBy == 'asc' ? 'selected' : '' }}>Price Low to High</option> <option value="desc" {{ request()->sortBy == 'desc' ? 'selected' : '' }}>Price High to Low</option> </select> </form> <!-- Loop through the $products collection instead of $category->products --> @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
View Tweaks:
- Removed
{{ csrf_field() }}: CSRF tokens aren't required for GET requests, so this was unnecessary. - Added
selectedattributes: This keeps the user's chosen sort option visible after the page reloads.
Optional: Pagination
If you have lots of products, replace $productsQuery->get() with $productsQuery->paginate(12) (adjust the number to fit your needs) and add pagination links in your view:
{{ $products->links() }}
That's it! Now when a user clicks "Phone", they'll see all products from Phone, Samsung, and Nokia categories, and can sort them by price or date.
内容的提问来源于stack exchange,提问作者Muhammad Abbas

