Тема
Smart Relations (Умные связи)
Концепция
Технологии и интеграции связаны с категориями услуг и отраслями через pivot-таблицы. Контроллеры ВСЕГДА используют pivot-связи модели, а не прямые запросы ко всем записям.
Pivot-таблицы
| Таблица | Связь | Строк |
|---|---|---|
technology_service_category | Technology ↔ ServiceCategory | ~414 |
industry_technology | Technology ↔ Industry | ~372 |
integration_service_category | Integration ↔ ServiceCategory | ~113 |
integration_industry | Integration ↔ Industry | ~242 |
integration_technology | Integration ↔ Technology | ~144 |
service_technology | Service ↔ Technology | varies |
article_technology | Article ↔ Technology | ~18 |
article_service_category | Article ↔ ServiceCategory | ~89 |
case_study_technology | CaseStudy ↔ Technology | ~24 |
package_technology | Package ↔ Technology | ~45 |
Покрытие: 123/123 технологий и 50/50 интеграций имеют связи с категориями И отраслями.
Модели — pivot relations
Technology
php
$technology->serviceCategories() // belongsToMany via technology_service_category
$technology->industries() // belongsToMany via industry_technology (with is_primary, sort_order)
$technology->integrations() // belongsToMany via integration_technology
$technology->services() // belongsToMany via service_technologyIntegration
php
$integration->serviceCategories() // belongsToMany via integration_service_category
$integration->industries() // belongsToMany via integration_industry
$integration->technologies() // belongsToMany via integration_technologyКонтроллеры — правила
show() методы
php
// ПРАВИЛЬНО — из pivot
$categories = $technology->serviceCategories()->has('services')->orderBy('sort_order')->get();
$industries = $technology->industries()->whereNotNull('seo_slug')->orderBy('name')->get();
// НЕПРАВИЛЬНО — все подряд
$categories = ServiceCategory::all();
$industries = Industry::all();withCategory() / withIndustry() методы
php
// ПРАВИЛЬНО — другие категории/отрасли этой же сущности из pivot
$otherCategories = $technology->serviceCategories()
->where('service_categories.id', '!=', $category->id)
->has('services')
->orderBy('sort_order')
->get();
$otherIndustries = $integration->industries()
->where('industries.id', '!=', $industry->id)
->whereNotNull('seo_slug')
->orderBy('name')
->get();Blade-шаблоны
На страницах show рендерится плоский список из pivot (не иерархия root→children):
blade
@foreach($industries as $industry)
@if($industry->seo_slug)
<a href="{{ route('technologies.with-industry', [$brand, $industry]) }}">
{{ $industry->name }}
</a>
@endif
@endforeachНе использовать $rootIndustry->children — это вызовет lazy loading ВСЕХ дочерних из БД.
Сидеры
Порядок в DatabaseSeeder важен:
GranularRelationsSeeder— основные связи (integration_technology, article_, case_study_, package_*, integration_industry)SmartRelationsSeeder— дополнения tech↔category, tech↔industry, int↔category
Оба используют insertOrIgnore() — идемпотентны, безопасно запускать повторно.
Добавление новой технологии/интеграции
- Создать запись в соответствующей таблице (migration/seeder/admin)
- Добавить маппинг в
SmartRelationsSeeder:seedTechCategories()— slug технологии → массив slug'ов категорийseedTechIndustries()— slug технологии → массив slug'ов отраслейseedMissingIntegrationCategories()— slug интеграции → массив slug'ов категорий
- Запустить:
docker exec gulaev-app php artisan db:seed --class=SmartRelationsSeeder
Anti-patterns
| Паттерн | Проблема | Решение |
|---|---|---|
ServiceCategory::all() в контроллере | Показывает ВСЕ категории, не связанные | $model->serviceCategories() |
Industry::all() в контроллере | Показывает ВСЕ отрасли | $model->industries() |
| Хардкод type→industry в контроллере | Связи не в БД, невозможно управлять | Pivot-таблица + Filament |
$rootIndustry->children в Blade | N+1, грузит ВСЕ дочерние | Плоский список из pivot |
| Новая сущность без маппинга | Пустые секции на странице | Добавить в SmartRelationsSeeder |