ЧПУ – человекопонятные URL

Как разбить URL на переменные?
Вводная:
Помогите разбить URL на переменные: https://name.com/content/article/1/

Ответ:

  • Получить URL так $url = $_SERVER[‘REQUEST_URI’];
  • Далее, воспользоваться командой explode

Вариант от Нечто (mod_rewrite и разбор пути средствами PHP):

Правила mod_rewrite:

# Закомментируйте ниже, если скрипт должен перехватывать:
RewriteCond %{REQUEST_FILENAME} !-f # - запросы к существующим файлам
RewriteCond %{REQUEST_FILENAME} !-d # - запросы к существующим директориям

RewriteRule (.*)$  index.php/$1 [QSA]

Благодаря вышеприведенным правилам, вместо виртуального /some/interesting/page/ будет вызван URI /index.php/some/interesting/page, а обращения к реально существующим файлам обрабатываться не будут.

Разбор пути:

<?php
// Достаем переменную окружения, содержащую путь; если она не задана, находимся в корне.
$URI = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';

// Разбиваем путь на элементы, фильтруя пустые значения и
// восстановливая численную последовательность индексов.
// Пустые значения образуются при разбивке путей типа //another///interesting/page///.  
$URIelements = array_values(array_filter(explode('/', $URI)));

print_r($URIelemets);
?>

Впоследствии элементы запроса можно перебрать на предмет каких-то ключевых системных значений, а также склеить обратно с помощью implode.

Как удобнее хранить данные о разделах сайта
Вариант 1
Базовые поля таблицы:
id – int – идентификатор страницы
title – varchar – заголовок страницы
url – varchar – полный URL страницы

Вообщем-то, весь вариант заключается в том, что сохраняются все полные URL для всех существуют страниц, то есть нет страниц с параметром, который может принимать любое значение(числовое, например), либо все допустимые значения строго описаны.

ИТОГО:
* Метод прост тем, что не нужны большие усилия для его реализации, достаточно взять пришедший URL и попыпаться выбрать его из базы
* Он не может быть использован при большом числе динамического контента(например, лента новостей), так как в таком случае получается довольно большая база и необходимо контролировать все изменения, влекущие к созданию нового URL

Вариант 2
Базовые поля таблицы(pages):
id – int – идентификатор страницы
url – varchar – URL страницы
handler – varchar – файл-обработчик

Довольно распространена ситуация, когда вывод подкатегорий почти не отличается друг от друга. Единственным отличием является группа, к которой принадлежит контент.
Например, /news/sport и /news/tv из раздела /news различаются только тем, что, например, в sql-запросе в условии WHERE стоят разные группы(sport и tv, соответственно).
Тогда как в /news группы вообще не учитываются.
Из-за этого совершенно излишне делать отдельные обработчики для каждого URL, точнее, создание обработчиков сведётся к копированию и изменению 1 запроса(не забывайте про то, что при таком копировании при обнаружении какой-то ошибки вам придётся копировать всё заново).

При таком подходе необходимо добавить в БД 1 запись:

url handler
/news/ news.php

При загрузке страницы для выбора нужного файла-обработчика действуем по следующей схеме:

  1. пытаемся выбрать из БД полный
  2. если нет такого URL, то отрезаем от него часть и возвращаемся в п.1 для полученного URL
  3. отдаём управление handler’у

ИТОГО:

  • Удобно для хранения страниц с параметрами (дата, номер поиска и т.д.), так как парамеры будут извлечены в отдельный массив
  • Под опредeлённые условиях можно создать отдельный обработчик(например, существует категория новостей, где используется отличный от остальных новостей формат, либо данные хранятся в другой таблице)
  • В случае, если не существует страница, например, /shop, то отобразаится главная страница, что может быть нежелательные, хотя и можно модифицировать код, чтобы при определённых условиях(например, нет страницы в «первом уровне») выдавалась страница с 404 ошибкой

Примеры поиска:

<?php
        // Удаляем лишние слеши, в начале и конце $url точно будет /
        $url = preg_replace('#/+#', '/', '/'.$_SERVER['REQUEST_URI'].'/');
        $flag  = FALSE; // Найдем обработчик?
        $param = array(); // Массив для параметров

        while ($url != '/' && !$flag) {
                // Пытаемся выбрать обработчик для страницы
                $result = mysql_query("SELECT `handler` FROM `pages` WHERE `url`='".$url."' LIMIT 1", $db); 
                if (!mysql_num_rows($result)) {
                        $url     = substr($url, 0, strlen($url) - 1); // Отрезаем / на конце $url
                        $pos     = strrpos($url, '/') + 1;
                        $param[] = substr($url, $pos); // Отрезаем параметр
                        $url     = substr($url, 0, $pos); // Получаем новый $url
                } else {
                        $row     = mysql_fetch_assoc($result); // Получаем строку
                        $handler = $row['handler']; // Помещяем обработчик в $handler
                        $flag    = TRUE;
                }
                unset($result);
        }
        // "Переворачиваем" массив с параметрами, чтобы они шли по порядку следования в URL
        $param = array_reverse($param); 
?>

Данный код является ТОЛЬКО примером
В итоге мы получаем все параметры в массиве $param, файл-обработчик в $handler.

Вариант 3
Базовые поля таблицы(pages):
id – int – идентификатор страницы
parent_id – int – родитель
handler – varchar – обработчик, занимающийся страницей
url – varchar – часть URL между //

Метод заключается в том, что используется древовидная структура и построение дерева происходит в PHP-скрипте.

Допустим, есть URL: /news/sport/smth
Выбираем из базы:

SELECT *
FROM `pages`
WHERE `url`
IN (
'news', 'sport', 'smth'
)

Дальше остается только немного обработать результаты выборки.

ИТОГО:
* При большой вложенности метод должен быть довольно медленным
* Во многом он зависит от реализации
* В зависимости от реализации, метод позволяет разбирать структуры подобные такой: <static>/<param>/<static>

Вариант 4
Базовые поля таблицы аналогичны Варианту 3.

Вариант работоспособен, если для хранения разделов будет использоваться Adjacency List.
Вариант генерации запроса, не требующего дальнейшего разбора:

<?php
$url = trim(strtolower($url), '/');
$_url = explode('/', $url);
$_url = array_filter($_url, 'strlen');

$sql1 = 't0.id';
$sql2 = $sql3 = '';
for($i = 1, $c = sizeof($_url); $i < $c; $i++)
{
    $sql1 = "IF (t" . $i . ".id IS NOT NULL, t" . $i . ".id, " . $sql1 . ")";
    $sql2 .= "+IF(t" . $i . ".id IS NULL,0,1)";
    $sql3 .= "LEFT JOIN structure t" . $i . " ON (t" . $i . ".parent_id = t" . ($i-1) . ".id AND t" .
                  $i . ".url = " . $this->db->Quote($_url[$i]) . ") ";
}
$pageId = $this->db->_Query("SELECT " . $sql1 . " AS id, 1" . $sql2 . " AS count FROM structure t0 " .
                                           $sql3 . " WHERE t0.parent_id = 0 AND t0.url = " . $this->db->Quote($_url[0]));
$this->debug->Trace_R($pageId);
?>

Получаем:

Array
(
[id] => 3
[param] => 1
)

Я думаю надо помимо id еще сразу тянуть все нужные данные (название раздела, обработчик и т.д.).

ИТОГО:
* Максимальное число вложенностей ограничено засчёт того, что существует максимальное число JOIN в БД
* Метод может быть медлительным, так как используется JOIN, при больших объёмах базы и большой вложенности скрипт может вылезти за временное ограничение

Вариант 5
Базовые поля страницы(pages):
id – int – идентификатор страницы
handler – varchar – обработчик
url – varchar – регулярное выражение для URL, соответствующее обработчику

Лирическое отступление: вообще, мне этот метод был навеян python’овским django, в котором именно при помощи регулярных выражений перечислялась обработка всех допустимых URL. Данный вариант является реализацией части функционала, предоставляемым Django с использованием БД.

Суть метода заключается в том, что в базе хранятся регулярные выражения, которые соответствуют URL. Выборка нужных страниц осуществляется посредством использования MySQL’оного REGEXP.

Если немного расширить данный способ, то в PHP при помощи одного preg_match можно получить все параметры. А дальше уж насколько фантазии хватит.

ИТОГО:
* Говорят, что регулярные выражения в исполнение My SQL не так уж быстры, а при большом числе страниц, это медлительность может стать катастрофической
* Метод позволяет обрабатывать URL, построенных абсолютно любым способом, который можно описать при помощи регулярных выражений

Почти все приведённые варианты(кроме 3 и 5) страдают 1 проблемой: они не могут разбирать URL формата: /<static>/<param>/<static>/<param> и подобные

Добавление слeша в конце
Вводная:
Нужно добавлять слeш в конце URL, если он отсутствует.

Решение:

RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ $1/ [R]

ЧПУ при помощи ошибки 404 и POST данные
Вводная:
Сегодня стал детально разбираться с ЧПУ через обработку 404 ошибки. Вроде все замечательно работает, но вот я не могу получить данные из $_POST.

Ответ:
Суть заключается в использовании директивы Error Document для перенаправления всех несуществующих запросов на один скрипт.
Главный существенный недостаток Error Document – невозможность перехвата POST-данных!
Чтобы получить POST данные формируйте action формы на реально существующий URL.
Также, к недостаткам относится то, что засоряется error.log сервера, что мешает выявлению настоящих ошибок, которые необходимо исправить.

ЧПУ при момощи Multi Views или Force Type Apache
Вводная:
Слышал, что существует способ «скрытия» расширения PHP-файлов при помощи конфигурирования непосредственно Apache.

Ответ:
Первым является способ с использованием Mutli Views.
Сам способ заключается в том, что у реальносуществующего файла скрывается расширение. То есть до какого-то момента URL реальный, а после – нет.
Он «включается» добавлением в .htaccess или к настройкам сервера:

Options +MultiViews

На самом деле, это более понятно на примере.
Допустим, у нас есть файл article.php, лежащий в корне Web-сервера. Тогда запросы вида /article/php_security1, /article/sessions будут обрабатываться скриптом article.php. Далее, уже всё зависит от разбора в скрипте остальной части URL(если требуется).

Второй способ основывается на Force Type.
Способ позволяет добиться аналогичного эффекта при использовании Force Type. Мы называем файл просто article, а не article.php.
И в .htaccess или конфигурации сервера добавляем:

<Files article>
        ForceType application/x-httpd-php
</Files>

Так что теперь файл article будет интерпретироваться PHP.


Оставьте комментарий