| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- <?php
- namespace App\Http\Controllers;
- /*
- * CatalogController — публичный каталог и страница автомобиля.
- *
- * Кеш: plain array (не объекты) → без __PHP_Incomplete_Class при unserialize.
- * Ключ кеша включает все параметры фильтра + страницу пагинации.
- * filterOptions() кешируется отдельно — пересобирать при каждом запросе дорого.
- */
- use App\Models\Car;
- use Illuminate\Http\Request;
- use Illuminate\Pagination\LengthAwarePaginator;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\View\View;
- class CatalogController extends Controller
- {
- private const CACHE_TTL = 86400; // 24 часа
- private const PER_PAGE = 12;
- public function index(Request $request): View
- {
- // ── Читаем фильтры из GET ──────────────────────────────────────
- $conditions = array_filter((array) $request->input('condition', []));
- $countries = array_filter((array) $request->input('country_origin', []));
- $fuels = array_filter((array) $request->input('engine_type', []));
- $make = trim((string) $request->input('make', ''));
- $bodyType = trim((string) $request->input('body_type', ''));
- $priceTo = $request->filled('price_to') ? (int) $request->input('price_to') : null;
- $priceFrom = null;
- // price_range из быстрого подбора: "1500000", "1500000-3000000", "5000000-"
- $priceRange = $request->input('price_range', '');
- if ($priceRange !== '') {
- if (str_ends_with($priceRange, '-')) {
- $priceFrom = (int) rtrim($priceRange, '-') ?: null;
- } elseif (str_contains($priceRange, '-')) {
- [$from, $to] = explode('-', $priceRange, 2);
- $priceFrom = $from !== '' ? (int) $from : null;
- $priceTo = $to !== '' ? (int) $to : $priceTo;
- } else {
- $priceTo = (int) $priceRange ?: $priceTo;
- }
- }
- $year = $request->filled('year') ? (int) $request->input('year') : null;
- $sort = $request->input('sort', '');
- $page = $request->integer('page', 1);
- // ── Строим ключ кеша ──────────────────────────────────────────
- $filterData = compact('conditions', 'countries', 'fuels', 'make', 'bodyType', 'priceFrom', 'priceTo', 'year', 'sort');
- ksort($filterData);
- $cacheKey = 'catalog.'.md5(serialize($filterData)).'.p'.$page;
- $cached = Cache::remember($cacheKey, self::CACHE_TTL, function () use (
- $conditions, $countries, $fuels, $make, $bodyType, $priceFrom, $priceTo, $year, $sort, $page
- ) {
- $query = Car::where('status', 'active');
- if ($conditions) {
- $query->whereIn('condition', $conditions);
- }
- if ($countries) {
- $query->whereIn('country_origin', $countries);
- }
- if ($fuels) {
- $query->whereIn('engine_type', $fuels);
- }
- if ($make) {
- $query->where('make', $make);
- }
- if ($bodyType) {
- $query->where('body_type', $bodyType);
- }
- if ($priceFrom) {
- $query->where('price_rub', '>=', $priceFrom);
- }
- if ($priceTo) {
- $query->where('price_rub', '<=', $priceTo);
- }
- if ($year) {
- $query->where('year', $year);
- }
- match ($sort) {
- 'price_asc' => $query->orderBy('price_rub'),
- 'price_desc' => $query->orderByDesc('price_rub'),
- 'year_desc' => $query->orderByDesc('year'),
- default => $query->orderByDesc('id'),
- };
- $paginator = $query->paginate(self::PER_PAGE, ['*'], 'page', $page);
- return [
- 'total' => $paginator->total(),
- 'items' => $paginator->getCollection()->map->getAttributes()->all(),
- ];
- });
- // Восстанавливаем Car из атрибутов (без unserialize объекта)
- $items = collect($cached['items'])->map(fn ($a) => (new Car)->setRawAttributes($a, true));
- $cars = new LengthAwarePaginator(
- $items, $cached['total'], self::PER_PAGE, $page,
- ['path' => $request->url(), 'query' => $request->query()]
- );
- $filterOptions = $this->filterOptions();
- $activeFilters = compact('conditions', 'countries', 'fuels', 'make', 'bodyType', 'priceFrom', 'priceTo', 'year', 'sort');
- return view('pages.catalog', compact('cars', 'filterOptions', 'activeFilters'));
- }
- public function show(int $id): View
- {
- // Детальная страница: без кеша — запросы редкие, зато данные всегда свежие
- $car = Car::where('status', 'active')->findOrFail($id);
- $similar = Car::where('status', 'active')
- ->where('id', '!=', $car->id)
- ->where(fn ($q) => $q->where('make', $car->make)->orWhere('body_type', $car->body_type))
- ->orderByDesc('id')
- ->limit(3)
- ->get();
- return view('pages.car', compact('car', 'similar'));
- }
- // Опции для фильтров — кешируются отдельно
- private function filterOptions(): array
- {
- return Cache::remember('catalog_filter_opts', self::CACHE_TTL, function () {
- $base = Car::where('status', 'active');
- return [
- 'makes' => (clone $base)->select('make')->distinct()->orderBy('make')->pluck('make')->toArray(),
- 'body_types' => (clone $base)->whereNotNull('body_type')->where('body_type', '!=', '')->select('body_type')->distinct()->orderBy('body_type')->pluck('body_type')->toArray(),
- 'countries' => (clone $base)->whereNotNull('country_origin')->select('country_origin')->distinct()->orderBy('country_origin')->pluck('country_origin')->toArray(),
- 'years' => (clone $base)->select('year')->distinct()->orderByDesc('year')->pluck('year')->toArray(),
- 'price_min' => (int) ((clone $base)->whereNotNull('price_rub')->min('price_rub') ?? 0),
- 'price_max' => (int) ((clone $base)->whereNotNull('price_rub')->max('price_rub') ?? 50000000),
- ];
- });
- }
- }
|