Разработка каталога товаров: Вывод, фильтрация и пагинация
Студент реализует публичную часть магазина: вывод списка товаров из БД, фильтрацию по категориям и удобную постраничную навигацию.
Архитектура каталога: Жизненный цикл запроса
Каталог товаров — это лицо интернет-магазина. Реализация этой части проекта (Project-Based Learning) требует интеграции Маршрутизатора, Контроллера, Модели и Представления (MVC). Когда пользователь переходит по адресу /products, маршрутизатор вызывает метод index в ProductController. Задача контроллера — обратиться к модели Product, получить данные из базы, возможно, отфильтровать их и передать в шаблон Blade для генерации HTML.
Если в базе тысячи товаров, мы не можем вывести их все на одной странице — это убьет производительность сервера и браузера клиента. Здесь на помощь приходит пагинация (постраничная навигация). Laravel делает пагинацию невероятно простой. Вместо метода get(), который извлекает все записи, мы используем метод paginate(). Laravel сам вычислит смещение (OFFSET) и лимиты (LIMIT) для SQL-запроса на основе GET-параметра ?page= в URL.
// App\Http\Controllers\ProductController.php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller {
public function index(Request $request) {
// Создаем базовый запрос
$query = Product::query();
// Фильтрация по категории, если передан параметр category_id
if ($request->has('category_id')) {
$query->where('category_id', $request->input('category_id'));
}
// Решение проблемы N+1: жадная загрузка категорий
$query->with('category');
// Пагинация: 12 товаров на страницу
$products = $query->paginate(12);
return view('products.index', compact('products'));
}
}
Проблема N+1 и Жадная загрузка (Eager Loading)
В коде выше вы заметили метод with('category'). Это критически важный момент для оптимизации производительности, который новички часто упускают. Представьте, что вы выводите 12 товаров на странице, и для каждого товара в шаблоне Blade вы пишете: {{ $product->category->name }}. Если вы не используете with(), Laravel сделает 1 запрос для получения 12 товаров, а затем 12 отдельных SQL-запросов для получения категории каждого товара. Итого: 1 + 12 = 13 запросов. Если товаров 100, будет 101 запрос. Это и есть проблема N+1.
Метод with('category') включает жадную загрузку (Eager Loading). Laravel сделает всего 2 запроса: первый — на получение 12 товаров, второй — на получение всех категорий, чьи ID присутствуют в этих 12 товарах (запрос вида SELECT * FROM categories WHERE id IN (...)). Затем фреймворк сам свяжет эти данные в памяти. Это колоссально снижает нагрузку на базу данных.
Какую проблему решает использование метода with() в Eloquent ORM?
Шаблонизатор Blade: Вывод товаров и ссылок пагинации
Получив объект Paginator в переменной $products, мы передаем его в представление. Blade предоставляет удобные директивы для работы с массивами и объектами. Мы используем директиву @foreach для перебора товаров и вывода их карточек.
Самое приятное в Laravel — это генерация HTML-кода для кнопок пагинации (1, 2, 3, Next, Previous). Для этого достаточно вызвать метод links() у объекта пагинатора: {{ $products->links() }}. По умолчанию Laravel использует стили Tailwind CSS для пагинации, но вы можете легко переключить их на Bootstrap в сервис-провайдере, если ваш проект использует этот CSS-фреймворк. Также важно помнить, что если вы используете фильтрацию (например, ?category_id=5), вам нужно сохранить эти GET-параметры в ссылках пагинации, используя метод appends().
<!-- resources/views/products/index.blade.php -->
<div class="grid">
@foreach($products as $product)
<div class="card">
<h3>{{ $product->name }}</h3>
<!-- Благодаря with('category') этот вызов не создает доп. SQL-запрос -->
<p>Категория: {{ $product->category->name }}</p>
<p>Цена: ${{ number_format($product->price, 2) }}</p>
<form action="{{ route('cart.add') }}" method="POST">
@csrf
<input type="hidden" name="product_id" value="{{ $product->id }}">
<button type="submit">В корзину</button>
</form>
</div>
@endforeach
</div>
<!-- Вывод ссылок пагинации с сохранением параметров фильтрации -->
<div class="pagination-wrapper">
{{ $products->appends(request()->query())->links() }}
</div>