PHP-роутинг (Routing) для новичков
02-02-2016Reading time ~ 6 min.PHP 146724
Роутинг — это маршрутизация: входящий URL разбирается специальным образом и по его результату выполняется определенный код. С роутингом напрямую связано понятие ЧПУ (человекопонятные урлы), которое позволяет исключить в адресах сложные параметры. Например вместо http://сайт/admin/new-page
пришлось бы использовать http://сайт/admin.php?action=new-page
Любой входящий URL на сервере разбирается по единому стандарту. Полностью приводить документацию не буду (см. как пример функцию parse_url), важно лишь понять, что в адресе передается параметр path (путь на сервере), которого на сервере реально может не быть. Например в адресе http://сайт/admin
каталога admin
реально может не существовать.
То есть сервер, получив такой адрес, попытается найти каталог admin
, но не найдя его, выдаст 404-страницу (not found).
Чтобы исключить такой вариант, серверу указывается, что для всех несуществующих каталогов и файлов, подключать php-файл (обычно index.php
).
Делается это в файле .htaccess
с помощью Apache-модуля mod_rewrite. Вот довольно типовой вариант (MaxSite CMS):
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.php/$1 [L,QSA]
Тут главная строчка с RewriteRule — именно она определяет шаблон входящего адреса (в примере это регулярное выражение) и что с ним делать. В данном примере будет подключен index.php с параметрами после слэша.
Строчка RewriteCond %{REQUEST_FILENAME} !-f / -d
указывает исключить из обработки реально существующие на сервере файлы и каталоги.
Похожий вариант, только чуть короче, от WordPress:
RewriteRule . /index.php [L]
Вариант LPF:
RewriteRule ^(.*)$ /index.php?page=$1 [QSA]
Здесь принудительно добавляется query-параметр page.
Еще один распространенный вариант (пожалуй самый «типовой»):
RewriteRule (.*) index.php?$1 [QSA,L]
Все эти RewriteRule-правила делают простую вещь: как бы «преобразуют» входящий адрес в набор query-параметров. Например адрес http://сайт/admin
превратится в http://сайт/index.php?admin
Посмотрите на полный код .htaccess
:
<IfModule mod_rewrite.c> RewriteEngine on RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) /index.php?$1 [QSA,L] </IfModule>
Если это какой-то подкаталог, то он указываетс в RewriteBase и как путь к php-файлу. Например каталог на сервере route
:
<IfModule mod_rewrite.c> RewriteEngine on RewriteBase /route/ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) /route/index.php?$1 [QSA,L] </IfModule>
Теперь, все адреса на сервере будут передаваться в файл index.php
, а исходный адрес сохранится в виде query-параметра.
Если в index.php разместить
<?php print_r($_GET);
То мы можем увидеть query-параметры. В PHP за это отвечается суперглобальная переменная $_GET
. Например для http://сайт/admin
это будет admin
, для http://сайт/admin/new-page
— admin/new-page
.
Таким образом, с помощью .htaccess
происходит первая часть роутинга, где мы получаем готовый $_GET
.
Кстати, насчет.htaccess
WordPress. Он не создает$_GET
, поэтому придется использовать$_SERVER['REQUEST_URI']
в который включается подкаталог. Работать с таким адресом уже будет сложней.
Второй этап роутинга выполняется полностью на PHP. Получив $_GET
нужно решить что с ним делать. Например если адрес admin, подключить файл admin.php
.
Существуют несколько принципиально разных подходов в организации роутинга. Наиболее популярный подход — это когда в адресе передаётся «действие», которое описывается через php-класс. Такой подход хорошо описан в CodeIgniter:
example.com/class/function/id/
Например пусть будет класс admin в нём метод edit, принимающий параметр $id.
example.com/admin/edit/23 class admin { function edit($id) { ... } }
Это сильно утрированный пример, но он хорошо показывает соответствие адреса и php-класса.
Другой вариант похожий, но используется не классы, а функции.
example.com/admin/edit/23 function admin($params) { ... }
То есть функция — это первый сегмент, а остальные выступают уже как парметры. Встречается более «продвинутый» вариант.
example.com/admin function admin($params) { ... } example.com/admin/edit/23 function admin_edit($params) { ... }
То есть имя функции строится по сегментам URL.
Третий, тоже распространенный вариант — адрес указывает на подключаемый файл.
example.com/admin $fn = 'pages/admin.php'; if (file_exists($fn)) reqiure($fn); else reqiure('pages/404.php');
Здесь все файлы хранятся в каталоге pages и подключаются только если реально существуют. Если файла нет, то подключается предопределенный 404-файл.
Если сайт представляет собой сложный проект, то как правило используют роутинг на основе php-классов. Так его проще поддерживать и развивать. Если же сайт состоит из небольшого количества страниц, то как правило используют подключаемые файлы, где один файл — это одна страница.
Строго говоря, роутинг «на классах» тоже использует «файловое» подключение. Вначале подключается файл с кодом класса, а уже после этого выполняется сам класс.
В задачу роутинга входит не только необходимое «действие», но и валидация входящего адреса и его лексический разбор.
Адреса могут строиться по шаблону. Например какой-то адрес должен содержать только номер, а не текст (например example.com/admin/edit/23
, но не example.com/admin/edit/hello
). Или адрес может быть неизменным, но обработчик будет меняться от вида запроса GET или POST. В одном случае нужно подключить одну функцию/файл, в другом — другой. Бывают и более сложные задачи, все их перечислять нет смысла, поэтому появились разного рода php-библиотеки для организации роутинга.
Свой «велосипед» не изобретал только ленивый, но я отмечу довольно известный FastRoute, который вобрал в себя наиболее типичные решения.
В первую очередь это использование регулярных выражений при задании правил, например:
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
Примерно такой же подход используется и в роутинге CodeIgniter.
products/shirts/123 $route['products/([a-z]+)/(d+)'] = "$1/id_$2";
То есть входящий адрес должен соответствовать шаблону и только в этом случае он «сработает».
В FastRoute реализована поддержка POST и GET-запросов. Такая возможность интересна, хотя на больших проектах такие вещи лучше делать на уровне самого «действия». Но это уже тонкости. Про эту библиотеку я упоминаю в первую очередь из-за того, что она достаточно популярна и уже используется в нескольких интересных проектах: Slim и Lumen.
Для небольших проектов, конечно же, FastRoute будет избыточна, поэтому можно ограничиться вариантом попроще, да и мозги потренировать. :-)
ТЗ. Пусть роутинг будет обрабатывать адреса вида http://сайт/blog/contact/map
, по которому будет подключен файл content/blog/contact/map/index.php
, где каталог content — это общее хранилище всех страниц сайта, а index.php — обязательный файл с кодом страницы. Роутинг должен проверять реальный файл и если его нет, то подключать 404-страницу. Если никаких параметров нет, то это home-страница. Пусть они будут предопределены.
Решение. По сути задача сводится к преобразованию входящего адреса в путь на сервере. У нас есть массив $_GET
, где первый ключ и есть входящий URL. Дальше формируем путь к файлу и если он есть, то подключаем. Если нет, то подключаем 404-страницу.
Весь код в 2 строчки:
<?php $fn = ($p = key($_GET)) ? 'content/' . $p . '/index.php' : 'content/home/index.php'; (file_exists($fn)) ? require $fn : require 'content/404/index.php'; # end of file
Это простой роутинг для простых проектов. Достаточно создать в content каталог, как он станет доступен по одноименному адресу.
Круто, спасибо!
Таки неплохо. Спасибо.
Объяснение так себе на тройку. В стиле "а вот есть такое, и есть такое", но деталей нет от слова вообще. Понять по этому тексту что куда и зачем можно только если уже знаешь все сам
Все четко написано!
А то нашел такую же статью просто выложил код и подписал:
код говорит сам за себя думаю не стоит объяснять!!!
Очень хороший материал и понятный сначала, но.... зачем в самом низу пример "Весь код в 2 строчки". Это же статья для новичков, понятно что вы крутой программист и можете все упаковать в 2 строки, но зачем усложнять, разложите на простые if(), пусть хоть 10 строк получится, а то ничего в конце не понял
Это сокращенная запись if/else. По идее даже новички их должны знать. Это же базовый синтаксис PHP. :-)
Хорошая основа для новичков под дальнейший PHP обработчик:
Ну так как реализовать роутинг тот, что в Codeigniter ?
В CodeIgniter (и других фреймворках) два ключевых момента. Первый — шаблон url, на основании которого происходит сравнение текущего адреса. И второй — php-класс, который соответствует этому шаблону. Шаблон url по сути — обычная регулярка, когда она срабатывает, (авто)подключается php-класс с указанным методом.