Описание фронтовой части (routes/site/__site.php) проекта на Laravel Коротко: фронт сайта опирается на одну таблицу контента (quzat_site_content) с «шаблонными» типами страниц (template): - 8 — страница «Материал» (разводящая) - 9 — страница «Город» (разводящая) - 12 — страница «Материал в городе» (микролендинг, MIC) Вся фронтовая логика завязана на контроллеры сектора __Site и сервис rCache, который собирает и нормализует данные для шаблонов и SEO. Роуты явным образом мапятся на методы build_page контроллеров и (при продакшене) могут полностью кешироваться как готовый HTML (gzip) с TTL, возвращаясь напрямую из кеша до выполнения действий контроллера. 1) Карта роутинга (site) Файл роутов: routes/site/__site.php:1 - Домашняя: `/` → `SiteIndex::build_page` - Research: `/research` и `/research/{alias}` → индекс/страница блога-исследований - Поиск по сайту: `/search` → `SiteSearch::build_page` - DM (справочник ДМ): `/dm`, `/dm/{type}`, `/dm/{type}/{item}` → `DmIndex`, `DmType`, `DmItem` - Данные для карт (gzipped JSON): `/data_to_map/{hash}` → `MaterialInCity__map::map_from_cache` - Фотобанк: `/fotobank` → `Fotobank::build_page` - Сплавы: `/splavi`, `/splavi/{alias}`, `/splav/{alias}` → `SplavIndex`, `SplavCategory`, `SplavItem` - Объявления (offer): `/offer` (редирект на список), `/offer/offer-list`, `/offer/offer-add` - About: `/about`, `/about/subscribe`, `/about/unsubscribed`, `/about/{alias}` - Контакты: `/contacts` - Лендинги: `/most-wanted`, `/opredelenie-splavov` (+ POST `/opredelenie-splavov/json`) - Биржевые котировки: `/rates/{any?}` → `RatePageOld` - Пункты приема (общая): `/punktyi-priema` - Блог: `/blog`, `/blog/page/{page?}`, `/blog/{alias}` - Заказ из Китая: `/order-china` (+ POST submit) - Сервис покупателя: `/buyer-service` (+ POST submit) - Маркет (отдельная логика): `/market` (+ категория, продукт, корзина, заказ, поиск) - Team promo: `/team-promo` - Город (разводящая): `/{city_alias}` с ограничением по списку слагов → `City::build_page` - Материал (разводящая): `/materials/{material_alias}` → `Material::build_page` - Материал в городе (MIC): `/{city_alias}/{material_alias}` (where: город из whitelista) → `MaterialInCity::build_page` (name: site__material_in_city) - Страницы пунктов приема: `/org/{city_alias}/{user_id_encoded}` → `PunktPriemItem::build_page` Примечания - Ограничение на `city_alias` реализовано как регулярное объединение слагов (переменная `$cities_str`). Это whitelist городов, допускаемых к генерации URL MIC и городских страниц. - Конвенция контроллеров фронта: почти везде единый метод `build_page()`, который формирует переменные страницы и вызывает `site_view()`. 2) Базовый рендер и тотальный кеш Базовый класс фронта: `app/Http/Controllers/__Site/FrontSiteController.php:1` - `site_view(view_key, page_variables, ttl, cache_key)` - В продакшене и на домене `scraptraffic.com` при переданном `ttl` включает «total-cache»: - Пререндерит Blade в строку, кладет в кеш (gzip) под ключ `cache_key` на `ttl`. - Возвращает обычный HTTP‑response (без gzip в ответе). - `try_from_total_cache(cache_key)` — ранний выход с отдачей gzipped‑контента из кеша (если есть), с заголовком `Content-Encoding: gzip`. - `default_variables` — общий набор переменных для шапки/футера (телефоны, email и т. п.), подставляются, если контроллер их не выставил. 3) Материал в городе (MIC) — центральная логика Роут: `routes/site/__site.php:~150` `Route::get('{city_alias}/{material_alias}', [MaterialInCity::class, 'build_page'])->where('city_alias', $cities_str)->name('site__material_in_city');` Контроллер: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity.php:1` - Наследуется от `Mic`, который в свою очередь от `FrontSiteController` и задает базовый префикс view: `__site.sectors.mic.layouts.` (`Mic::$base_blade`). - `build_page()` 1) Пытается отдать страницу из total‑cache по ключу `total_cache__Mic_Mic__{city}__{material}`. 2) `prepere_rCache()` ищет MIC‑ресурс (template=12) по точному `uri = {city_alias}/{material_alias}` через модель Modx Resource и готовит кэш данных (см. rCache ниже). Если ресурс не найден — 404. Если он снят с публикации и нет валидного `admin_test` подписи в URL — 301 редирект на родителя или на город. 3) SEO: заголовки собираются через `MaterialInCity__seo_title::prepare_meta_title()` с учетом цены, валюты, склонений по городу/материалу и возможной маски. H1 берется из `rCache->mic['pagetitle']`. 4) Контакты: `set_page_contacts()` переносит override полей из MIC в `default_variables`. Затем подтягиваются пункты приема (активные в `rCache->punkt_priem`, неактивные — `punkti_priema_other()`). 5) Карта: `MaterialInCity__map::prepare_map()` собирает массив меток для Yandex Maps + формирует хэш и кладет json (gzipped) без TTL в кеш под ключ `map_from_cache__{hash}`. Отдельный роут `/data_to_map/{hash}` отдает этот json (с `Content-Encoding: gzip`). 6) Строятся данные блока цены, таблицы подкатегорий, кастомной таблицы цен (если есть JSON в `team_custom_material`), текстового контента (с обработкой «MODX‑тегов»), фотобанка и «похожие материалы». 7) Подбор Blade‑шаблона: по умолчанию `material_in_city`, но для особых alias используется `service_cleaning` или `special__dragocennye-metally`. 8) Итоговый вызов `site_view()` с total‑cache на 30 дней. Переменные шаблона (основное): - `id`, `template` (наследие, для CSS‑классов на body) - `mic`, `city`, `material` — нормализованные массивы из rCache - `other_mic` — все MIC по этому городу (кроме текущего), с предрасчитанными ценами с учетом валюты города - `price_block` — строковые представления цены и диапазонов (минимальные объемы, множественные условия, валюта и ед. измерения) - `subcategory_price_table` — список доступных дочерних позиций MIC (если не скрыт флагом `remove_basic_material_table`) - `custom_price_table` — массив структуры пользовательской таблицы (если есть в `team_custom_material.json_data`) - `text_content` — контент с заменой MODX‑синтаксиса в `[[...]]` - `fotobank_html` — HTML‑блок(и) фотобанка по материалу - `punkti_priema_active`, `punkti_priema_other` — пункты приема с телефонами и служебными полями - `map_data_active`, `map_data_other` — данные для карты (coords, prices_list, hash) - `page_blocks` — пользовательские блоки (Stg\PageBlocks) - `related_materials` — короткий прайс‑лист близких материалов по городу - `breadcrumbs` — хлебные крошки: Главная → Город → Материал (в городе) Сервисы и вспомогательные классы, вовлеченные в MIC: - rCache: `app/Services/__Site/rCache.php:1` - Кэширует и агрегирует: - Общие ресурсы материалов (template=8) и городов (template=9) + их TV‑поля (Modx TV) в `rCache->cached[resource_id]`. - Для материалов вычисляет `top_level_category_id` и сохраняет `forced_em` (принудительная ед. изм.). - Для городов устанавливает валютный код, курс и коэффициент пересчета (Helpers::get_price_currency_for_city()). - Метод `prepare_material_in_city($mic)`: - Присваивает `rCache->mic`, `rCache->material`, `rCache->city`, добавляет валютные поля (`price_currency_*`), переносит `forced_em` из материала в MIC. - `prepare_fields()` подтягивает из материала в MIC некоторые поля по умолчанию: `min_volume`, `forced_em`, `mask_title`, `similar_material_ids`, а также изображение и индекс для калькулятора. - `prepare_contacts()` — заполняет телефон и email по умолчанию (или из PhoneBasicResourceId), форматирует через `Helpers::phone_format()`. - `prepare_punkt_priem()` — активные пункты приема для данного MIC (SQL join по `team_material_to_punkt_priem`, складывает телефоны `phone/phone2/phone3`, WhatsApp, Telegram). - `prepare_city_alias()`, `prepare_material_price()`, `prepare_corrected_material_price()` — нормализация цены (пересчет по валюте города и ед. измерению: кг/т/г), хранит в `material_price_inunit` + `material_price_unit`. - Дополнительно: `set_all_city_materials_to_rCache()` собирает все прочие MIC по городу с конвертированными ценами, чтобы отрисовать «Другие материалы в городе». - SEO заголовок: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__seo_title.php:1` - Приоритет: `mic.longtitle` → маска `mask_title` → дефолтная формула «Сдать {материал} {цена} в {город}» с попыткой укладываться в ~65 символов. - Использует `Helpers::price_convert()` и склонения названий городов/материалов из TV‑полей. - Карта: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__map.php:1` - `prepare_map($punkti_priema)` — формирует список меток и небольшой «прайс‑лист рядом», сериализует json и складывает gzipped в кеш под ключ `map_from_cache__{hash}`. - `map_from_cache(Request)` — отдает кэш по хэшу как `application/json` + `Content-Encoding: gzip`. Роут: `routes/site/__site.php:~90` (`/data_to_map/{hash}`). - Блок цен: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__price_block.php:1` - Строки для «цены за единицу» и, при наличии MIGX JSON `migx_price_full`, — таблица диапазонов по объему (пересчет валюты и единиц измерения, названия валют из Helpers). - Таблица подкатегорий: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__subcategory_price_table.php:1` - Список дочерних MIC (template=12, parent=текущий MIC), учитывает флаг отключения базовой таблицы из `team_custom_material.remove_basic_material_table` и список исключений. - Фотобанк: `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__fotobank.php:1` - Находит категорию фотобанка по `page_id = material.id`, отрисовывает вложенные категории/фото (лимиты, тримминг описаний), готовит HTML‑кусок для вставки в шаблон. - Текстовый контент с «модкс‑тегами»: `app/Http/Controllers/__Site/ModxEmulateSyntaxHandler.php:1` - Поддерживает конструкции вида `[[*field]]`, `[[!snippet?¶m=`val`]]`, `[[$chunk]]` и некоторые предопределенные элементы, эмитируя их (часть заглушки, часть — реальный рендер через view‑чанки). - Цены/валюта: `app/Services/__Site/Helpers.php:1` - `price_convert()` — пересчитывает числа в нужную валюту города + масштаб (кг/т/г), принимает `forced_em`. - `get_price_currency_for_city(city_id)` — возвращает код валюты города, курс (через `HelpersApi::rates_cbr`), коэффициенты и строковые названия валют для UI. - `basic_material_links(city_id)` — базовый список материалов для внутренних блоков навигации. - Получение цены «на лету»: `app/Services/__Site/GetPrice.php:1` - По `uri` либо (`city_id`, `material_id`) достает фактическую цену MIC, при отсутствии — подставляет базовую цену материала, затем конвертирует через `Helpers::price_convert()`. - Списки цен: `app/Services/__Site/PricesList.php:1` - По resource_ids либо (city_id + material_ids) собирает компактные строки прайса для карточек, с учетом принудительной единицы материала и валюты города. Есть режим «коротких строк» для мобильных блоков. 4) Страницы «Город» и «Материал» (разводящие) - Город: `/{city_alias}` → `app/Http/Controllers/__Site/Sector/Mic/City.php:1` - Ищет ресурс (template=9) по `alias`. Подтягивает TV и PageBlocks, собирает «пункты приема» в городе (с агрегацией/сортировкой), строит карту (reuse логики MIC). - Строит полное дерево доступных MIC в городе (и отдельный список «услуг», stg_page_type=2), предварительно конвертируя цены под валюту города. Отмечает родительские узлы, у которых есть опубликованные дети. - Кеширует HTML на 14 дней через `site_view()`. - Материал: `/materials/{material_alias}` → `app/Http/Controllers/__Site/Sector/Mic/Material.php:1` - Ищет ресурс (template=8) по `alias`, строит «список городов, где есть MIC для этого материала» и «список подкатегорий» (в т.ч. сбор категорий верхнего уровня). - Кеширование HTML на 14 дней. 5) Маркет (независимый раздел) Роуты с префиксом `/market` из `routes/site/__site.php:…` - Индекс, категории, карточка товара, заказ/корзина, поиск — изолированный от MIC стек контроллеров (`App/Http/Controllers/__Site/Sector/Market/*`). - Для заказа: `Order::store` (POST). Для корзины: `Cart::add/remove/update/list`. - Раздел логически отделен от «лома» и MIC, общий код только уровень базового FrontSiteController/шаблонов. 6) Прочие информационные разделы - DM (справочник ДМ): `/dm`, `/dm/{type}`, `/dm/{type}/{item}` — каталог и карточки, вьюхи под `__site/sectors/dm`. - Splavi (марочник сплавов): `/splavi...` — индекс, категории, карточки. - Research (блог/исследования): `/research...` - About (техстраницы): `/about...` (+ subscribe/unsubscribed используется для подписки на уведомления по offer). - Rates: `/rates/{any?}` — отдача/рендер котировок в старом формате. - Fotobank: `/fotobank` — сбор фотографий вторсырья; фото материалов также выводятся на MIC через `fotobank_html`. - Лендинги: `order-china`, `buyer-service`, `team-promo`, `kalkulyator-sdelki`, `calc-weight`, `opredelenie-splavov` — самостоятельные контроллеры и страницы. 7) Интеграция с «наследием» MODX - Хранилище контента — таблица `quzat_site_content` и связанные `quzat_site_tmplvar_contentvalues` (TV‑поля), `quzat_site_tmplvar_templates` (доступные TV по шаблону), `quzat_system_settings` / `quzat_clientconfig_setting` (настройки). - Модель `App/Models/Modx/Resource.php:1` инкапсулирует связи (tv, parent/children, город/материал, блоки страницы и т.д.). - В контроллерах (MIC, City, Material) TV‑поля подмешиваются в нормализованные массивы/структуры rCache. - `ModxEmulateSyntaxHandler` разбирает упрощенный MODX‑синтаксис в текстовом контенте (см. `MaterialInCity::prepere_text_content()`). 8) Данные, валюты, единицы измерения - Валюта и ее строковые представления задаются функцией `Helpers::get_price_currency_for_city(city_id)` (код, курс, «₽/тенге/сум/манат/Br», короткие/длинные подписи и т.д.). Курс берется из `HelpersApi::rates_cbr()`. - `Helpers::price_convert(value, curs, coef, …, forced_em)` конвертирует число и нормализует единицы (кг/т/г) с учетом принудительного `forced_em`. - Это используется почти везде: в подсчетах на карточках, в SEO‑титлах, в таблицах цен и списках похожих материалов. 9) Поведение 404/301 и публикация MIC - `MaterialInCity::prepere_rCache()` явно проверяет случай, когда MIC снят с публикации (`st_unpublished`). Если в запросе нет валидного `admin_test` (sha1 от `{city}/{material}`), посетителя перенаправляют на родителя или на страницу города (301). Это исключает попадание в индекс «черновых» MIC. 10) Ключевые точки для рефакторинга (наблюдения) - Явные «template‑id» (8/9/12/63) в SQL и коде лучше инкапсулировать в именованные константы и репозитории/спеки по типам страниц. - rCache выполняет много обязанностей (сбор TV, валют, телефонных блоков, расчет цен, выборки «соседей»). Имеет смысл декомпозировать на отдельные сервисы/классы: ContentRepository, PriceService, ContactService, MapService. - Конвертация валют/единиц сейчас размазана по нескольким классам (Helpers, PricesList, price_block и пр.) — стоит унифицировать API и формат возврата. - Вставки HTML (fotobank_html) возвращаются как строка — можно перевести в view‑компоненты с четкой структурой данных. - MODX‑эмуляция (обработчик тегов) — выделить слой рендера контента и явно ограничить/логировать неподдерживаемые элементы. - Тотальный кеш: учесть домены/субдомены, варьировать ключ по `Host` или окружению, чтобы избежать смешения кэша. - Переменные «по умолчанию» (телефоны/email) — привести к единому источнику и строгости типов, чтобы не прокидывать строки «как есть». 11) Быстрая шпаргалка по MIC - Роут: `/{city}/{material}` (город из whitelist). - Контроллер: `MaterialInCity::build_page()`. - Данные: через `rCache->prepare_material_in_city()` на основе Modx Resource (template=12, точный `uri`). - Карта: `MaterialInCity__map` + `/data_to_map/{hash}`. - Цены: `MaterialInCity__price_block`, `PricesList`, `GetPrice`. - Контент: `ModxEmulateSyntaxHandler`. - Кеш: total‑cache 30 дней (prod, host= scrapttraffic.com). - Вью: `resources/views/__site/sectors/mic/layouts/material_in_city.blade.php:1` (или спец. шаблоны по alias). 12) Где смотреть дальше (файловые ориентиры) - Роутинг (site): `routes/site/__site.php:1` - MIC контроллер и модули: - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity.php:1` - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__seo_title.php:1` - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__map.php:1` - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__price_block.php:1` - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__subcategory_price_table.php:1` - `app/Http/Controllers/__Site/Sector/Mic/MaterialInCity__fotobank.php:1` - Сервисные слои: - `app/Services/__Site/rCache.php:1` - `app/Services/__Site/Helpers.php:1` - `app/Services/__Site/GetPrice.php:1` - `app/Services/__Site/PricesList.php:1` - Модель ресурсов (MODX): `app/Models/Modx/Resource.php:1` - Разводящие: - Город: `app/Http/Controllers/__Site/Sector/Mic/City.php:1` - Материал: `app/Http/Controllers/__Site/Sector/Mic/Material.php:1` - Вьюхи MIC: `resources/views/__site/sectors/mic/layouts/`