Ещё до того как заработает сессия и до первого запроса к базе, о
пришедшем уже кое-что известно — из заголовков запроса. Откуда он
подключился, с чего смотрит и на каком языке говорит. Эти три вещи
отдают методы семейства Agent*.
Общее у них одно, и оно важное: данные приходят снаружи, от клиента. Ими определяют, что показать, но доверять им как проверенным сведениям нельзя — подделать заголовок может кто угодно.
Все три метода — для корневого скрипта. Внутри кешируемого модуля их вызывать нельзя: ответ «запечётся» в кеш и достанется посетителям, к которым он не относится. В модули эти сведения приходят уже в виде выбранной группы шаблонов, языка страницы или явно переданного параметра.
$ip = MELBIS()->AgentIp();Метод возвращает адрес посетителя с учётом того, что сайт может
стоять за прокси или CDN, — разбираются заголовки пересылки, а не только
прямой адрес соединения. Для журналов, антифрода и определения региона
используйте именно его, а не $_SERVER['REMOTE_ADDR']
напрямую.
От сессии метод не зависит и работает независимо от того, вызван ли
DefineSession.
$device = MELBIS()->AgentDevice();Метод возвращает одну из трёх строк — phone,
tablet или desktop — и нужен там, где витрина
отдаётся в разных версиях с одного адреса: по нему выбирается группа
шаблонов до вызова Run().
Определение идёт в два шага. Сначала спрашивается сам браузер:
Chromium по HTTPS присылает клиентскую подсказку
Sec-CH-UA-Mobile, и это не догадка, а ответ браузера о
себе. Если подсказки нет — Safari, Firefox и поисковые роботы её не
шлют, — разбирается User-Agent по наборам ключей: сначала
планшетные, затем телефонные. Не совпало ничего —
desktop.
Планшет отделён от телефона намеренно: относить его к мобильной версии или к полной — решение проекта, а не движка.
$device = MELBIS()->AgentDevice();
if ( $device == 'phone' ) MELBIS()->TemplateSet('mobile');
MELBIS()->Run($entry_point, $entry_param);Как устроены группы шаблонов и почему две версии витрины могут жить на одном домене — в разделе «Группы шаблонов».
Наборы ключей при необходимости переопределяются — целиком или по
одному, до первого вызова AgentDevice:
MELBIS()->DefineAgentKeys([
'tablet' => '/ipad|tablet|playbook|silk|kindle|nexus 7/i'
]);Ключ должен быть phone или tablet, значение
— готовое регулярное выражение вместе с ограничителями и флагами.
Неизвестный ключ или неверный шаблон останавливают парсер с ошибкой:
иначе опечатка в регулярном выражении молча отправила бы весь мобильный
трафик в полную версию. Подсказка Sec-CH-UA-Mobile не
настраивается — портить хороший сигнал нечем.
Вердикт вычисляется один раз за запрос, поэтому звать метод повторно ничего не стоит.
Отдавая по одному адресу разную вёрстку, добавьте к ответу заголовок
Vary: User-Agent — так поисковые системы и промежуточные
кеши узнают, что содержимое страницы зависит от устройства.
$lang = MELBIS()->AgentLanguage(['ru', 'uk'], 'uk');Метод разбирает заголовок Accept-Language и возвращает
язык, который посетитель предпочитает больше других, — двухбуквенным
кодом. Первый аргумент — языки, которые умеет витрина; второй — что
вернуть, если ни один из них посетителю не подходит. Без аргументов
метод отдаёт самый желанный язык вообще, каким бы он ни был.
Нужен он там же, где AgentDevice, — в корневом скрипте,
чтобы встретить нового посетителя на его языке: подставить нужный
языковой префикс в адрес или сразу вызвать LanguageSet.
Почему нельзя просто взять первые две буквы заголовка. Браузер присылает не один язык, а список с весами:
uk-UA,uk;q=0.9,ru;q=0.8,en-US;q=0.7,en;q=0.6
Вес q (от 0 до 1, по умолчанию 1) и задаёт предпочтение
— порядок элементов формально ничего не значит, хотя браузеры обычно
пишут по убыванию. На заголовке en-US,ru;q=0.9,uk;q=0.8
наивное «первые два символа» дадут en, и посетитель, прямо
попросивший русский вторым пунктом, получит язык по умолчанию.
AgentLanguage выбрасывает из списка всё, чего витрина не
умеет, и берёт максимальный вес среди оставшегося.
Ещё три вещи метод делает сам: приводит региональные варианты к языку
(uk-UA и uk — это один uk, из них
берётся больший вес), пропускает q=0 — это явный отказ от
языка, а не слабое предпочтение, — и игнорирует * и
мусор.
Заголовок разбирается один раз за запрос, поэтому повторные вызовы с разными списками языков ничего не стоят.
Заголовка может не быть вовсе — его не шлют поисковые роботы и часть API-клиентов. Тогда метод вернёт значение по умолчанию, и это нормальный путь, а не ошибка: робот должен получать основную языковую версию, а не результат угадывания.