Времена, когда на солидном сайте можно было встретить в навигации ссылки типа site.com?page_id=2, кажется, прошли безвозвратно и хвала Богам. Но зачастую, молодые, только ставшие на путь программиста, разработчики бьются над вопросом: как организовать красивые url, как, например, на сайте города Волковыска.
Что такое mod_rewrite объяснять, я думаю, не стоит — слово, по крайней мере, слышали все. Мне на моей первой работе объясняли так: видишь наш сайт site.com и страничку портфолио — site.com/portfolio/web-applications/project1/. Я тут уже хотел сказать, мол, что не могли сделать что-то вроде: site.com?part=portfolio&part2=web-applications&project=project1, а лучше id_page=1&id_module=2&id_project=1, но меня определи, сказав: «Ты думаешь у нас столько папочек на сервере? А вот хрен тебе!».
Так мне на пальцах показали, что такое mod_rewrite и с чем его едят. Теперь, я себе не представляю ни одного проекта него. Как применять данную возможность каждый выбирает сам. Существует масса подходов для решения этой проблемы. Чуть ли не каждый разработчик вырабатывает свою концепцию по применению mod_rewrite. Сегодня я хочу рассказать, как я решил для себя эту проблему.
Итак, поехали. У меня все странички обрабатывает index.php. Для этого в файле .htaccess пишем
следующее:
DirectoryIndex index.php RewriteEngine On Options +FollowSymlinks RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*) index.php?%{QUERY_STRING}
Итак, теперь, получив адрес site.com/news/2/, наше приложение обратится к файлу index.php, при условии, что будет отсутствовать папка news.
В базе данных мы будем хранить данные о том, какой подсовывать шаблон при введенном адресе. Структура таблицы примерно следующая:
Имя поля | Тип данных | Описание |
---|---|---|
id_page | INTEGER | Идентификатор страницы. Автоинкриментное поле |
url | VARCHAR(255) | url для которого определен шаблон |
system | VARCHAR(255) | Системное имя страницы |
id_template | INTEGER | Идентификатор шаблона. Поскольку, один шаблон может быть использован для нескольких страниц, то целесообразнее хранить информацию о шаблонах в отдельной таблице |
id_premission | INTEGER | Идентификатор прав доступа, которым должен обладать пользователь, чтобы просматривать данную страницу |
status | INTEGER(1) | Активна страница или нет. Поле принимает значение 1 или 0 |
Теперь наша задача состоит в том, чтобы разобрать наш url и отобразить пользователю нужную информацию.
Небольшое отступление. Допустим мы будем отображать новости: url будет например news/2/, где 2 это идентификатор новости, следовательно хранить мы будем только информацию об url’е news/, а цифра 2 это просто идентификатор новости. Разбираем мы наш урл с помощью php следующим образом:
$mRequestUri = $_SERVER["REQUEST_URI"]; //получаем REQUEST_URI if ( $mRequestUri == '/' ) //если пользователь обратился к главной странице { $mPageUrl = $mRequestUri; } else { if ( $_SERVER['QUERY_STRING'] ) { $mPageUrl = preg_replace (array('/^\//', '/\/?\?'.RegexpEscape($_SERVER['QUERY_STRING']).'$/'), array('',''), $mRequestUri ).'/'; } else { $mPageUrl = preg_replace (array('/^\//', '/\/?\??$/'), array('',''), $mRequestUri ).'/'; } }
Теперь в переменной $mPageUrl будет url страницы без параметров, передаваемых методом $_GET. Функция RegexpEscape выглядит так:
function regexpEscape($str) { return preg_quote($str, '/'); }
Ну вот, теперь осталось самое главное — извлечь из базы данных информацию о страничке по её адресу:
SELECT id_page, id_template, url, system, SUBSTRING(\'$mPageUrl\' from length(url)+1 ) trail FROM site_pages WHERE \'$mPageUrl\' LIKE CONCAT(url,'%') AND status=1 ORDER BY length(SUBSTRING(\'$mPageUrl\' from length(url)+1 )) LIMIT 1
Последние строки запроса служат для следующего: у нас могут быть разные шаблоны для страниц к примеру: news/, news/politcs и news/sport/, но для страницы news/my-news/ должен использоваться шаблон для страницы news, а my-news должно быть параметром страницы. Что с ним делать каждый решает сам. Кстати, извлечение параметров (все что не вошло в url) происходит так: получив информацию о странице, мы загоняем её в массив, например $pageData. Теперь получаем параметры:
if ($pageData['trail']) { $urlParams = explode('/',$pageData['trail']); if($urlParams[count($urlParams)-1] == "") unset($urlParams[count($urlParams)-1]); }
Теперь при url, допустим, news/my-news/page-2/ и наличии в таблице данных о шаблоне только для страницы news/ массив $urlParams будет иметь вид:
array('my-news','page-2');
Теперь с имеющимися данными мы можем делать то, что требует наше приложение. Надеюсь данный урок принесет пользу начинающим и даже уже бывалым разработчикам.
Погода на улице паршивая, настроения нет. А тут почитал ваш блог — настрой поднимает -)
Да уж погода и правда, по крайней мере в Минске, ужасная. Далеко не лето, скорей конец сентября
Ребята подскажите,
я делал через rewrite_mod ЧПУ — типа site.ru/page
Но когда я создал папку page, то этот метод уже не работал, сайт отображал содержимое этой папки.
Как правильно делать ЧПУ, но чтобы папки не мешали и все файлы из этих папок нормально грузились?
В условия для реврайтинга есть такие строки:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
Они говорят, что правило срабатывает при отсутствии папки или файла. Уберите это и все должно получится
В очередной раз спасибо, хорошие что есть такие люди как вы, которые дают на самом деле полезные советы:)
Заходите еще или подписывайтесь на RSS — будет много интересного!!!
Пробовал.
Удаляю RewriteCond %{REQUEST_FILENAME} !-d
после этого все страницы не доступны. Кажется только главная доступна — «/»
Может я там еще что-то неправильно делал. 🙁
Попробую еще раз, потом если что еще спрошу.
Это просто отличный способ, сенг. Мы тут на похапэ.ру обсуждали недавно ЧП, но этот способ круче предложенного мной
Сам там частенько бываю 🙂 и даже учавствовал в обсуждении… Для вашего вопроса, что там был Вами поднят, этот способ подойдет лучше всего, я думаю!
Опять-таки побочная проблема. Вряд ли она кому-нибудь мешает, мне лично как то пофиг 🙂
Это прям в точку!!! По другому и не скажешь! 🙂
Что-то я не понял.
Ваш запрос вернет мне только информацию о первом сегменте УРЛа, все остальное будет идти в trail.
Запрос для такого УРЛа — admin/login/ (в базе тоже есть соответствующие записи) — вернет мне информацию только про «admin», а «login/» будет отправлен в trail, хотя мне-то как раз нужна информация про «login» (айдишки, темплейт и т.д)
Запрос для такого УРЛа — admin/login/error/ — опять вернет мне информацию только про «admin», а «login/error/» будет отправлен в trail, хотя мне-то как раз нужна информация про «error» (айдишки, темплейт и т.д)
(MySQL 5.1)
если у вас в Базе будет запись для admin/login/, то запрос вернет именно эту запись, а не для admin/
admin/login/error/ вернет шаблон который будет лежать в базе, но если у вас будет, например, запись в БД для admin/login/, а вы вызовете admin/login/error/, то в данном случае error пойдет уже как параметр
Мдя… получается, что в поле «url» у меня должно быть записано «admin/login/». Ненормальная структура. Обычно используют дерево — айдишка+парэнт_айдишка:
|id |pid| url | name |
———————-
| 1 | 0 |admin| Админ
| 2 | 1 |login| Логин
У вас же получается так:
|id | url | name |
———————-
| 1 | admin | Админ
| 2 | admin/login | Логин
Так? Тогда это ужасно. Представьте себе раздел «shop» с кучей подразделов.
Да у меня так как описано во втором случае, но если брать пример могазина, то получается вы хотите хранить в базе что-то вроде:
products/cleaner/
products/cleaner/bosch/
products/cleaner/lg/
…..
products/cleaner/miele/
Что неправильно, ибо например для пылесософ, а чаще длля всех продуктов шаблон одинаков, следовательно вам нужно будет хранить шаблон только для products/ и сделать табличку с продуктами, в котором хранить описание каждого продукта и поднимать их по параметрам
На мой взгляд это вполне рабочее решение, для примера есть сайт в котором так вот хранятся новости: шаблон для всех новостей один и он лежит в базе один раз, все осталбное берется по параметрам и выводится нужная новость