Статьи

Создание дружественных URL

Одной из главных причин использования серверных языков программирования, таких как PHP, является возможность динамического управления контентом. Часто случается, что лишь один скрипт управляет выводом всего контента в зависимости от переданных в URL параметров. Эта статья посвящена технике и методам отображения таких параметров в понятной и «дружелюбной» форме и их обработке в PHP-скриптах.
 
Чтобы вы поняли, о чем идет речь, рассмотрим пример. Сайт хранит свои статьи в таблице СУБД articles. Чтобы показать ту или иную статью, мы ссылаемся на нее по ID:
 
Это самый простой, но не самый лучший путь. Во-первых, если посетитель сайтапросмотрел много статей, то адресная строка его браузера будет переполнена различными ID и он не сможет вернуться к нужной статье не прибегая к помощи закладок или перехода на индексную страницу, так как не запомнит соответствия ID и статей. Во-вторых, что еще важнее, при таких адресах теряется важная для поисковиков информация, и они хуже индексируют сайт.
 
К примеру, сайт phpriot.com использует такой «человеко-понятный» URL для статей:
 
В этой статье будет рассказано, как программно прочитать такой URL и спроецировать полученные параметры на данные в вашей базе. В PHP существует несколько методов для решения подобных задач. Мы рассмотрим их все и обсудим «за» и «против» каждого метода.
 

Apache и mod_rewrite 

Первым методом, который мы рассмотрим, будет модуль mod_rewrite для веб-сервера Apache. Этот модуль работает по принципу разбора адреса URL и выделения параметров, которые и передаются в скрипт, по подготовленному вами шаблону.
 
Допустим, в корневой директории веб-сервера есть скрипт news.php (т.е. доступ к нему осуществляется как к http://example.com/news.php). Этот скрипт отвечает за вывод одной новости, ID которой передается в URL. Другими словами, если вам нужно получить новость с ID 63, то в обычной ситуации вы обратились к скрипту так:
 
Но вместо этого, мы сделаем URL лучше читаемым и нам понадобится обратиться к тому же скрипту как: http://example.com/news/63.html (в качестве примера).
 
В любом случае, для того, чтобы сделать такое обращение работоспособным, необходимо внести одно небольшое «правило» в файл конфигурации сервера httpd.conf или в файл .htaccess в директории сайта. Текст, который необходимо добавить в файл, может быть следующим:
 
RewriteEngine on RewriteRule ^/news/([0-9]+).html /news.php?news_id=$1 

Используя регулярное выражение, мы сопоставляем все запросы к веб-серверу, которые начинаются со строки «news/», а далее содержат любое количество цифр и строку «.html». Символы, которые заключены в квадратные скобки будут присвоены переменной $1 (символы в следующих квадратных скобках, были бы присвоены $2, $3 и т.д., но в нашем примере только одни квадратные скобки). Значение переменной $1 будет подставлено в конечный URL. Таким образом, произойдет «незаметная» замена URL и скрипт получит обычную $_GET-переменную $news_id. Вот пример:


<?php
$news_id = $_GET[`news_id`];
?>

Дополнительные параметры URL

Иногда могут возникать ситуации, когда в скрипт нужно передать дополнительные параметры. Применительно к нашему примеру с news.php, это может быть дополнительный параметр “print”, который указывает на то, что страницу нужно отобразить в версии для печати (технически это делается с помощью CSS, но CSS – это отдельная тема).
 
В обычном случае, вы бы обратились к news.php следующим образом:
 
Используя наш вариант с mod_rewrite, мы могли бы обратиться к скрипту как:
 
Однако, наше регулярное выражение никак не выделит параметр print, поэтому в
него нужно внести некоторые коррективы, используя внутреннюю переменную Apache %
{QUERY_STRING}. Мы просто добавим ее к news_id с амперсандом.
 
RewriteEngine on RewriteRule ^/news/([0-9]+).html /news.php?news_id=$1&%{QUERY_STRING}
 
Теперь оба параметра доступны через $_GET.


<?php
$news_id = $_GET[`news_id`];
$printVersion = isset($_GET[`print`]);
?>

Все это работает с помощью модуля mod_rewrite. Это очень мощный и многосторонний модуль, поэтому при его изучении вы легко можете столкнуться с проблемами.
Поначалу, к примеру, бывает сложно заставить его работать с вашими шаблонами. Существует несколько отладочных опцию с помощью которых вы можете попробовать решить свою проблему.
 

Использование директивы Apache – ForceType

Альтернативой mod_rewrite является диретива того же веб-сервера Apache – ForceType. Что же она делает? Она позволяет выполняться PHP-скриптам, у которых нет расширения .php.
 
Обычно, веб-сервер настраивается так, чтобы обрабатывать файлы с расширением php, как PHP-приложения, а файлы с другими расширениями (к примеру html) не обрабатывать интерпретатором.
 
Возвращаясь к нашему примеру с mod_rewrite, вместо файла news.php в корневой директории будет лежать файл news (без расширения). Он будет доступен как
 
Для этого необходимо внести изменения в httpd.conf или .htaccess и записать в них следующее:
 
<Files news> ForceType application/x-httpd-php </Files>

Теперь, для доступа к нашей статье мы сможем вызывать:
 
В этом примере, файл будет доступен напрямую и нам останется обработать только строку /63.html. Она сохранена в серверной переменной PATH_INFO.


<?php
echo $_SERVER[`PATH_INFO`];
// выведет `/63.html`
?>

Теперь нам нужно использовать регулярные выражения для извлечения числа 63 из строки. Перед тем как перейти к регулярным выражениям, скажу, что параметров после имени файла news может быть несколько (к примеру, news/sport/63.htm), в таких случаях, строку предварительно необходимо обработать функцией explode() с разделителем /, что разобьет ее на массив значений. Однако, в нашем примере параметр один, и мы коснемся
регулярных выражений.
 
Приведу пример регулярного выражения (совместимого с preg_match()), которое ищет строку, предваряемую слешем и завершаемую сочетанием .html. Затем все найденные совпадения помещаются в массив, из которого мы и будем брать article_id.
<?php
$path = $_SERVER[`PATH_INFO`];
preg_match(`!^/(d+).html$!`, $path, $matches);
// $matches[0] сохранит всю строку, в то время как $matches[1]
// сохранит совпадение по первым скобкам. Нужно проследить,
// чтобы эта строка была числом, поэтому приведем тип к int:
$news_id = (int) $matches[1];
?>
Обычно мы используем слеш как ограничитель выражения, но здесь для этого лучше использовать другие символы (в этом случае – «!»). Так же добавим, что здесь мы ищем одну 1 (+) или несколько цифр (d). Здесь мы должны экранировать точку, так как точка в синтаксисе регулярных выражений обычно означает «любой символ», а нам точка нужна именно как символ точки.
 
В целом, это все что касается данной темы. Теперь вы можете использовать $news_id так как и положено в скрипте. Если путь не будет соответсвовать регулярному выражению, то $news_id примет значение 0, после того как мы приведем адресную строку к типу integer. Такой статьи может не быть, поэтому ошибку будет нужно обработать.
 

Использование своего обработчика 404 ошибки

Вероятно, это самый запутанный способ достижения нужного нам результата, не смотря на его простоту и мощность. Используя преимущества обработки 404 ошибки в Apache, вам будет достаточно создать один контролирующий скрипт, который будет решать, как обрабатывать тот или иной запрос.

Конечно же, этот метод будет работать только в том случае, когда запрашиваемого файла действительно не существует в файловой системе. К примеру, если на вашем сайте хранятся картинки, вы сможете получить к
ним доступ напрямую и тогда обработчик ошибки 404 не будет задействован.
 
Стоит добавить, что вы можете использовать преимущества функции header() и послать вместо «404 File not found», заголовок «200 OK». В таком случае, конечный пользователь даже и не догадается, что запрашиваемый им файл реально не существует.
 
Пример использования
Основываясь на этой идее, вам не придется беспокоиться за то, разрабатываете вы систему для работы с новостями, или что-то большое с разными типами контента.

К примеру, посмотрите на URL: http://www.phpriot.com/articles/php/index.html. Вместо того, чтобы нам создавать физический путь на сервере, мы используем обработчик 404 ошибки для работы с путем /articles/php/index.html.
 
Реализация обработчика 404 ошибки
Здесь мы рассмотрим пример реализации все той же системы новостей, но заодно мы коснемся и других сфер, которые можно обрабатывать таким образом, включая странички сообщений об ошибках.
 
Первое, что нужно сделать, это установить свой обработчик 404-ой ошибки. Это нужно прописать в .htaccess или конфигурационном файле httpd.conf:
 
ErrorDocument 404 /handler.php
 
Данная директива означает, что все запросы к несуществующим на сервере файлам будут перенаправлены в файл handler.php в корне document_root. Соответственно, в этом скрипте можно будет обрабатывать все поступающие запросы. Для получения исходного адреса нужно использовать серверную переменную REDIRECT_URL.


<?php
$request = $_SERVER[`REDIRECT_URL`];
// разделим запрос на несколько частей
$parts = explode(`/`, $request);
// установим флаг,нашли ли мы контент content
$found = false;
array_shift($parts);
// теперь определим тип контента
switch ($parts[0]) {
case `news`:
// используем простой регэксп
preg_match(`!^(d+).html$!`, $parts[1], $matches);
$news_id = (int) $matches[1];
$output = getNewsArticle($news_id);
// эта функция на самом деле не существует
// но мы предположим, что она возвращает контент статьи
// или NULL если статьи не существует
if ($output !== null)
$found = true;
break;
case `articles`:
// здесь мы реализуем обработчик для вывода на экран
break;
default:
}
if ($found) {
// вывести заголовок, что контент найден, или отправить 404 ошибку
header(`HTTP/1.1: 200 OK`);
echo $output;
}
else {
header(`HTTP/1.0 404 Not Found`);
echo `File not found`;
}
?>

Конечно, этот скрипт еще достаточно «сырой», однако он наглядно демонстрирует принцип работы метода.

Выводы
В этой статье мы рассмотрели несколько способов манипуляции вашими адресами URL, с целью приведения их к более «человекочитаемой» форме. Это будет полезно как для лучшей индексации сайта поисковиками, так и для более прозрачной работы пользователей. В реальности, я полагаю, такие поисковики как Google справятся и с GET параметрами, но «человекочитаемые» URL будут более прозрачными и для них.

Полезные ссылки
- Apache 2.0 URL rewriting guide: http://httpd.apache.org/docs/2.0/misc/rewriteguide.html
- Apache ForceType directive : http://httpd.apache.org/docs/2.0/mod/core.html#forcetype
- Apache ErrorDocument directive http://httpd.apache.org/docs/2.0/mod/core.html#errordocument

=====
Автор: Phpinside
Оригинал статьи: http://www.phpriot.com/d/articles/php/application-design/searchengine-urls/index.html