PHP

ЧПУ на PHP. Прочь от ModeRewrite правил. Единая точка входа.

Человекопонятный URL (ЧПУ) - одна из самых часто затрагиваемых тем на различных форумах, посвящённых языку PHP. Можно до бесконечности спорить, нужны ли красивые URL-адреса для SEO-оптимизации, но факт того, что веб-сайт с ЧПУ выглядит аккуратно и профессионально отрицать глупо.

URL адрес вида
http://server.com/catalog/tv/samsung/5

выглядит опрятно и интуитивно понятно для пользователя, нежели адрес вида
http://server.com/catalog.php?product=tv&brand=samsung&page=5

Кроме того, более-менее продвинутый пользователь в строке адреса броузера может стереть или заменить определенный путь иерархии в URL адресе, попав тем самым в нужное для него место на сайте (об этом хорошо написал Лебедев ещё в 2000 году - "Боремся за чистоту урлов", "Дублирующая навигация").

ЧПУ на базе модуля Apache mod_rewrite
Долгое вря ЧПУ делались с помощью небезызвестного модуля веб-сервера Apache mod_rewrite, который предназначен для манипуляции URL адресами. Директивы для mod_rewrite обычно писались разработчиками в .htaccess файле конфигурации Apache и выглядели примерно так:
RewriteEngine on
RewriteBase /
 
# Досье пользователя.
RewriteRule ^users/([0-9]+)\.html$  userinfo.php?user_id=$1
# Список всех пользователей
RewriteRule ^users/?$           users.php?%{QUERY_STRING}
 
# Новости общим списком
RewriteRule ^news/?$            news.php
# Новости по разделам
RewriteRule ^news/([a-z_]+)/?$      news.php?cat=$1
# Страница одной новости
RewriteRule ^news/([0-9]+).html$    news.php?id=$1

Чем плох подобный механизм преобразований URL адресов, освоенный на mode_rewrite? Ничем не плох, но для разработки гибких веб-приложений он не подходит. В первую очередь потому, что преобразованием занимается сам mod_rewrite и система фактически завязана на файле конфигурации .htaccess. Мы не имеем возможности влиять на процесс преобразования, добавлять в автоматическом режиме новые виртуальные URL адреса, привязывать сложную логику к определённым виртуальным URL-адресам и делать многое другое.

ЧПУ на PHP
Конечно же, на одном PHP ЧПУ не сделать. Моудуль mode_rewrite как и раньше нужно использовать, но только лишь для того, что бы перенаправить все запросы к виртуальным ЧПУ-адресам в единую точку входа в приложении - в index.php. Для этого в .htaccess файле конфигурации можно прописать следующий код:
RewriteEngine on
RewriteBase /
 
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ [NC,L]
RewriteRule ^.*$ index.php [NC,L]

Данная запись означает буквально следующее: если запрошенный URL-адрес не является файлом, не является символической ссылкой и не является директорией, то подменить виртуальный адрес файлом index.php. При этом, суперглобальная переменная PHP $_SERVER['REQUEST_URI'] будет содержать именно запрошенный виртуальный адрес. Что это нам даёт? Фактически - безграничные возможности для манипулирования виртуальными адресами!

Пример № 1.
Во многих фреймворках ЧПУ строится по следующему принципу:
http://server.com/модуль/действие/параметр1/значение1/параметр2/значение2/параметрН/значениеН/

"Распарсить" такой URL и получить имя модуля, действие и параметры нет никаких проблем.

В данном случае под определением "модуль" и "действие" можно понимать что угодно:

Модуль - подключаемый файл, действие - функция или конструкция в блоке if-else
Модуль - класс, действие - метод класса
т.е. все зависит от вашей архитектуры приложения.

Далее по тексту "модуль" и "действие" будет также называться совокупным определением "обработчик".

Возьмем в пример такой URL-адрес:
http://localhost/guestbook/edit_message/id/123

и "распарсим" его с помощью нижестоящего кода, который поместим в единую точку входа - в index.php:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
 
// Назначаем модуль и действие по умолчанию.
$module = 'index';
$action = 'index';
// Массив параметров из URI запроса.
$params = array();
 
// Если запрошен любой URI, отличный от корня сайта.
if ($_SERVER['REQUEST_URI'] != '/') {
try {
// Для того, что бы через виртуальные адреса можно было также передавать параметры
// через QUERY_STRING (т.е. через "знак вопроса" - ?param=value),
// необходимо получить компонент пути - path без QUERY_STRING.
// Данные, переданные через QUERY_STRING, также как и раньше будут содержаться в
// суперглобальных массивах $_GET и $_REQUEST.
$url_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 
// Разбиваем виртуальный URL по символу "/"
$uri_parts = explode('/', trim($url_path, ' /'));
 
// Если количество частей не кратно 2, значит, в URL присутствует ошибка и такой URL
// обрабатывать не нужно - кидаем исключение, что бы назначить в блоке catch модуль и действие,
// отвечающие за показ 404 страницы.
if (count($uri_parts) % 2) {
throw new Exception();
}
 
$module = array_shift($uri_parts); // Получили имя модуля
$action = array_shift($uri_parts); // Получили имя действия
 
// Получили в $params параметры запроса
for ($i=0; $i < count($uri_parts); $i++) {
$params[$uri_parts[$i]] = $uri_parts[++$i];
}
} catch (Exception $e) {
$module = '404';
$action = 'main';
}
}
 
echo "\$module: $module\n";
echo "\$action: $action\n";
echo "\$params:\n";
print_r($params);

Вот так будет выглядеть результат для URL-адреса http://localhost/guestbook/edit_message/id/123:
$module: guestbook
$action: edit_message
$params:
Array
(
[id] => 123
)

Теперь, если у вас есть класс Guestbook, отвечающий за работу гостевой книги, то вы просто инстанцируете этот класс и вызываете его метод отвечающий за редактирование сообщения. Массив $params можно передать в метод в качестве аргумента метода:
$guestbook = new $module();
$guestbook->$action($params); // редактирование сообщения с ID = 123

но лучше всего при подобном подходе поместить все полученные переменные из парсинга URL адреса в суперглобальный массив $_REQUEST:
$_REQUEST = array_merge($_REQUEST, $params);

Если же будет запрошен ошибочный URL -адрес:
http://localhost/guestbook/edit_message/id/

то сработает исключение и в качестве обработчика будет фигурировать обработчик 404 ошибки:
$module: 404
$action: main
$params:
Array
(
)

Пример № 2.
Для понимания следующего подхода вам необходимы базовые знания Perl-совместимых регулярных выражений (POSIX).

Данный подход очень гибок и интересен, т.к. позволяет создавать практические любые виртуальные адреса, с абсолютно произвольной структурой и в отличие от предыдущего подхода - без упоминания в URL реального имени обработчика (модуля или действия). Суть метода заключается в следующем: каждый виртуальный URL-адрес, который может быть в системе, необходимо описать в виде регулярного выражения. Назначить для него обработчик. Если запрошенный URL совпадает с одним из таких регулярных выражений, то с помощью функций регулярных выражений нужно получить из URL необходимые данные, после чего передать их в обработчик. Пример:
// Конфигурация маршрутов URL проекта.
$routes = array
(
// Главная страница сайта (http://localhost/)
array(
// паттерн в формате Perl-совместимого реулярного выражения
'pattern' => '~^/$~',
// Имя класса обработчика
'class' => 'Index',
// Имя метода класса обработчика
'method' => 'index'
),
 
// Страница регистрации пользователя (http://localhost/registration.xhtml)
array(
'pattern' => '~^/registration\.xhtml$~',
'class' => 'User',
'method' => 'registration',
),
 
// Досье пользователя (http://localhost/userinfo/12345.xhtml)
array(
'pattern' => '~^/userinfo/([0-9]+)\.xhtml$~',
'class' => 'User',
'method' => 'infoInfo',
// В aliases перечисляются имена переменных, которые должны быть в дальнейшем созданы
// и заполнены значениями, взятыми на основании разбора URL адреса.
// В данном случае в переменную user_id должен будет записаться числовой
// идентификатор пользователя - 12345
'aliases' => array('user_id'),
),
 
// Форум (http://localhost/forum/web-development/php/12345.xhtml)
array(
'pattern' => '~^/forum(/[a-z0-9_/\-]+/)([0-9]+)\.xhtml$~',
'class' => 'Forum',
'method' => 'viewTopick',
// Будут созданы переменные:
// forum_url = '/web-development/php/'
// topic_id = 12345
'aliases' => array('forum_url', 'topic_id'),
),
 
// и т.д.
);

Как видно из примера, так нужно будет описать все виртуальные URL адреса проекта. Напишем обработчик таких URL адресов:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
 
// Тут нужно подключить через include файл с маршрутами, который описали выше.
 
// Назначаем модуль и действие по умолчанию.
$module = 'Not_Found';
$action = 'main';
 
// Массив параметров из URI запроса.
$params = array();
 
foreach ($routes as $map)
{
// Для того, что бы через виртуальные адреса можно было также передавать параметры
// через QUERY_STRING (т.е. через "знак вопроса" - ?param=value),
// необходимо получить компонент пути - path без QUERY_STRING, т.к. в ином
// случае виртуальный адрес попросту не совпадет ни с одним паттерном из массива $routes.
// Данные, переданные через QUERY_STRING, также как и раньше будут содержаться в
// суперглобальных массивах $_GET и $_REQUEST.
$url_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 
if (preg_match($map['pattern'], $url_path, $matches))
{
// Выталкиваем первый элемент - он содержит всю строку URI запроса
// и в массиве $params он не нужен.
array_shift($matches);
 
// Формируем массив $params с теми названиями ключей переменных,
// которые мы указали в $routes
foreach ($matches as $index => $value)
{
$params[$map['aliases'][$index]] = $value;
}
 
$module = $map['class'];
$action = $map['method'];
 
break;
}
}
 
echo "\$module: $module\n";
echo "\$action: $action\n";
echo "\$params:\n";
print_r($params);

Теперь при запросе
http://localhost/forum/web-development/php/12345.xhtml

мы получим информацию о нужном нам обработчике (классе и методе класса), а так же массив параметров, с помощью которых и получим из СУБД информацию о теме в форуме:
$module: Forum
$action: viewTopick
$params:
Array
(
[forum_url] => /web-development/php/
[topic_id] => 12345
)

Соответственно, инстанцирование нужного обработчика, т.е. класса Forum и запуск его метода
Теги:
чпу, moderewrite
Добавлено: 14 Сентября 2013 10:43:26 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...