Skip to content

Blueprint: Каталог с категориями (Catalog)

Когда применять

«Каталог услуг», «наши работы», «товары без корзины» — категории + карточки + фильтры. Витрина без транзакций.

Не применять для: магазина с корзиной/оплатой (Lunar/Bagisto — см. opensource/ecommerce), каталогов на 100k+ позиций с геопоиском (отдельная архитектура: Scout + PostGIS, как в проекте catalog).

Состав решения

  • Модели Category, Item (именовать по домену: Service, Work, Product) + миграции + фабрики
  • Контроллеры CategoryController@index/show, ItemController@show
  • Filament-ресурсы для обеих моделей
  • Blade: список категорий, категория с карточками и фильтрами, карточка
  • Breadcrumbs-компонент с Schema.org
  • Тесты

Схема данных

categories

СтолбецТипИндексПримечание
id, name, slug unique, sort_order, is_activeстандартный набор
parent_idFK nullable selfindex, nullOnDeleteтолько если ТЗ требует вложенность
descriptiontext nullableSEO-текст категории
meta_title / meta_descriptionstring nullable

Вложенность: 2 уровня — обычный parent_id c рекурсией в Blade; 3+ уровня и деревья в запросах — staudenmeir/laravel-adjacency-list.

items

СтолбецТипИндексCast / примечание
ididPK
category_idFK constrainedindexrestrictOnDelete — не терять карточки при удалении категории
namestring
slugstringunique (или composite с category_id)
pricedecimal(10,2) nullable / integer копейки«цена от»; nullable = «по запросу»
short_descriptiontext nullableдля карточек в списке
contentlongText nullableдетальная
attributesjson nullablecast array; характеристики без фильтрации по ним
is_activeboolean default trueindex
is_featuredboolean default falseдля главной
sort_orderunsignedSmallInteger default 0index
timestamps

Изображения — medialibrary (gallery + конверсии). Фильтруемые характеристики — отдельные столбцы или таблица атрибутов, не JSON (specs/migrations).

Правила реализации

  • Роуты: /catalog, /catalog/{category:slug}, /catalog/{category:slug}/{item:slug} со scopeBindings() — Laravel сам проверит принадлежность item категории (иначе дубли URL одной карточки под разными категориями — SEO-беда).
  • Фильтры в категории: whitelist параметров (сортировка, цена от/до, 1–3 ключевых признака) через query string; canonical — на чистый URL категории без параметров.
  • Листинг: ->with('media'), withCount где надо, пагинация из config; сортировка default — sort_order.
  • «Цена от» — вычислять при сохранении, не агрегировать на каждый рендер.
  • SEO: шаблоны мета для категорий/карточек с переменными ({category}, {price}) по specs/seo; breadcrumbs со Schema.org BreadcrumbList; всё активное — в sitemap.
  • Кеш: теги catalog (+ category:{id} при больших объёмах), flush в afterSave.
  • Поиск по каталогу: до ~10k позиций — where('name', 'ilike') c functional index; больше или «умный» поиск — Scout + Meilisearch (см. guide/recipes).

Пакеты

spatie/laravel-medialibrary, spatie/eloquent-sortable (drag&drop порядок), staudenmeir/laravel-adjacency-list (только при глубоком дереве).

Шаги

  1. Миграции + модели + scopes (active, featured, ordered) + фабрики.
  2. Filament: категории (reorder), карточки (галерея, цена, category select).
  3. Роуты со scopeBindings + контроллеры.
  4. Blade: категория с фильтрами, карточка с галереей, breadcrumbs.
  5. SEO-шаблоны мета + sitemap.
  6. Тесты: категория 200, карточка 200, карточка под чужой категорией 404, неактивное скрыто, фильтр не ломает пагинацию.

Чеклист приёмки

  • [ ] scopeBindings: карточка открывается только под своей категорией
  • [ ] Листинги без N+1; изображения — конверсии
  • [ ] Фильтры — whitelist, canonical без query-параметров
  • [ ] Неактивные категории/карточки скрыты везде включая sitemap
  • [ ] Breadcrumbs с разметкой на всех уровнях
  • [ ] Удаление категории с карточками запрещено (restrict), а не каскад

Внутренняя база знаний студии