Управление URL

Управление URL-адресами в веб-приложениях включает в себя два аспекта:

  1. Приложению необходимо разобрать запрос пользователя, поступающий в виде URL, на отдельные параметры.
  2. Приложение должно предоставлять способ формирования адресов URL, с которыми оно сможет корректно работать.

В приложениях на Yii эти задачи решаются с использованием класса CUrlManager.

Примечание: Вы можете не пользоваться Yii для генерации URL, однако, так делать не рекомендуется, потому как вы не сможете легко поменять URL приложения через конфигурацию без изменения кода.

1. Создание адресов URL

В принципе, адреса URL можно задать прямо в коде представлений контроллера, однако куда удобнее создавать их динамически:

$url=$this->createUrl($route,$params);

где $this относится к экземпляру контроллера; $route соответствует маршруту запроса, а $params является списком параметров GET для добавления к URL.

По умолчанию, адреса создаются посредством createUrl в get-формате. Например, при значениях параметров $route='post/read' и $params=array('id'=>100), получим такой URL:

/index.php?r=post/read&id=100

где параметры указаны в виде набора пар имя=значение, соединенных знаком &, а параметр r указывает на маршрут. Однако, этот формат не очень дружелюбен по отношению к пользователю.

Подсказка: Для того, чтобы сгенерировать URL с хештегом, к примеру, /index.php?r=post/read&id=100#title, необходимо передать параметр # следующим образом: $this->createUrl('post/read',array('id'=>100,'#'=>'title')).

Мы можем сделать так, чтобы адрес, приведенный в качестве примера выше, выглядел более аккуратно и понятно за счет использования формата path, который исключает использование строки запроса и включает все GET-параметры в информационную часть адреса URL:

/index.php/post/read/id/100

Для изменения формата представления адреса URL, нужно настроить компонент приложения urlManager таким образом, чтобы метод createUrl мог автоматически переключиться на использование нового формата, а приложение могло корректно воспринимать новый формат адресов URL:

array('components'=>array('urlManager'=>array(
            'urlFormat'=>'path',
        ),
    ),
);

Обратите внимание, что указывать класс компонента urlManager не требуется, т.к. он уже объявлен как CUrlManager в CWebApplication.

Подсказка: Адрес URL, генерируемый методом createUrl является относительным. Для того, чтобы получить абсолютный адрес, нужно добавить префикс, используя Yii::app()->request->hostInfo, или вызвать метод createAbsoluteUrl.

2. Человекопонятные URL

Если в качестве формата адреса URL используется path, то мы можем определить правила формирования URL, чтобы сделать адреса более привлекательными и понятными с точки зрения пользователя. Например, мы можем использовать короткий адрес /post/100 вместо длинного варианта /index.php/post/read/id/100. CUrlManager использует правила формирования URL как для создания, так и для обработки адресов.

Правила формирования URL задаются путем конфигурации свойства rules компонента приложения urlManager:

array('components'=>array('urlManager'=>array(
            'urlFormat'=>'path',
            'rules'=>array(
                'pattern1'=>'route1',
                'pattern2'=>'route2',
                'pattern3'=>'route3',
            ),
        ),
    ),
);

Правила задаются в виде массива пар шаблон-путь, где каждая пара соответствует одному правилу. Шаблон правила — строка, которая должна совпадать с путём в URL. Путь правила должен указывать на существующий путь контроллера.

Кроме показанного выше способа задания правил, можно описать правило с указанием дополнительных параметров:

'pattern1'=>array('route1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false)

Начиная с версии 1.1.7, можно использовать показанный ниже формат. То есть паттерн указывается как элемент массива, что позволяет указать несколько правил одного паттерна:

array('route1', 'pattern'=>'pattern1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false)

Здесь массив содержит список дополнительных параметров для правила. Возможно указать следующие параметры:

  • pattern: паттерн, который будет использован при сопоставлении и создании URL. Данная возможность доступна с версии 1.1.7.

  • urlSuffix: суффикс URL, используемый исключительно для данного правила. По умолчанию равен null, что означает использование значения CUrlManager::urlSuffix.

  • caseSensitive: учитывает ли правило регистр. По умолчанию параметр равен null, что означает использование значения CUrlManager::caseSensitive.

  • defaultParams: GET-параметры по умолчанию (имя=>значение) для данного правила. При срабатывании правила параметры будут добавлены в $_GET.

  • matchValue: должны ли значения GET-параметров при создании URL совпадать с соответствующими подвыражениями в основном правиле. По умолчанию параметр равен null, что означает использование значения CUrlManager::matchValue. При значении параметра false правило будет использовано для создания URL только если имена параметров совпадают с именами в правиле. При значении true значения параметров дополнительно должны совпадать с подвыражениями в правиле. Стоит отметить, что установка значения в true снижает производительность.

  • verb: тип HTTP запроса (например, GET, POST, DELETE), для которого работает данное правило. По умолчанию равен null, что означает работу правила с любыми HTTP запросами. Если необходимо указать несколько типов запросов, их надо разделить запятыми. В том случае, когда правило не совпадает с текущим типом запроса, оно пропускается на этапе разбора запроса. Данная опция используется только для разбора запроса и введена для поддержки URL в стиле REST. Данная возможность доступна с версии 1.1.7.

  • parsingOnly: использовать ли правило только на этапе разбора запроса. По умолчанию параметр равен false, что означает, что правило используется как для разбора запроса, так и для построения URL. Данная возможность доступна с версии 1.1.7.

3. Использование именованных параметров

Правило может быть ассоциировано с несколькими GET-параметрами. Эти параметры указываются в шаблоне правила в виде маркеров следующим образом:

<ParamName:ParamPattern>

где ParamName соответствует имени GET-параметра, а необязательный ParamPattern — регулярному выражению, которое используется для проверки соответствия значению GET-параметра. Если ParamPattern не указан, то параметр должен соответствовать любым символам, кроме слэша /. В момент создания URL маркеры будут заменены на соответствующие значения параметров, а в момент обработки URL, соответствующим GET-параметрам будут присвоены результаты обработки.

Для наглядности приведем несколько примеров. Предположим, что наш набор правил состоит из трех правил:

array(
    'posts'=>'post/list',
    'post/<id:\d+>'=>'post/read',
    'post/<year:\d{4}>/<title>'=>'post/read',
)
  • Вызов $this->createUrl('post/list') сгенерирует /index.php/posts. Здесь было применено первое правило.

  • Вызов $this->createUrl('post/read',array('id'=>100)) сгенерирует /index.php/post/100. Применено второе правило.

  • Вызов $this->createUrl('post/read',array('year'=>2008,'title'=>'a sample post')) сгенерирует /index.php/post/2008/a%20sample%20post. Использовано третье правило.

  • Вызов $this->createUrl('post/read') сгенерирует /index.php/post/read. Ни одно из правил не было применено.

При использовании createUrl для генерации адреса URL, маршрут и GET-параметры, переданные методу, используются для определения правила, которое нужно применить. Правило применяется в том случае, когда все параметры, ассоциированные с правилом, присутствуют среди GET-параметров, а маршрут соответствует параметру маршрута.

Если же количество GET-параметров больше, чем требует правило, то лишние параметры будут включены в строку запроса. Например, если вызвать $this->createUrl('post/read',array('id'=>100,'year'=>2008)), мы получим /index.php/post/100?year=2008. Для того, чтобы лишние параметры были отражены в информационной части пути, необходимо добавить к правилу /*. Таким образом, используя правило post/<id:\d+>/* получим URL вида /index.php/post/100/year/2008.

Как уже говорилось, вторая задача правил URL — разбирать URL-запросы. Этот процесс обратный процессу создания URL. Например, когда пользователь запрашивает /index.php/post/100, применяется второе правило из примера выше и запрос преобразовывается в маршрут post/read и GET-параметр array('id'=>100) (доступный через $_GET).

Примечание: Использование правил URL снижает производительность приложения. Это происходит по той причине, что в процессе парсинга запрошенного URL CUrlManager пытается найти соответствие каждому правилу до тех пор, пока какое-нибудь из правил не будет применено. Чем больше правил, тем больший урон производительности. Поэтому в случае высоконагруженных приложений использование правил URL стоит минимизировать.

4. Параметризация маршрутов

Мы можем использовать именованные параметры в маршруте правила. Такое правило может быть применено к нескольким маршрутам, совпадающим с правилом. Это может помочь уменьшить число правил и, таким образом, повысить производительность приложения.

Для того, чтобы показать параметризацию маршрутов, используем следующий набор правил:

array(
    '<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>' => '<_c>/<_a>',
    '<_c:(post|comment)>/<id:\d+>' => '<_c>/read',
    '<_c:(post|comment)>s' => '<_c>/list',
)

Мы использовали два именованных параметра в маршруте правил: _c и _a. Первый соответствует названию контроллера и может быть равен post или comment, второй — названию action-а и может принимать значения create, update или delete. Вы можете называть параметры по-другому, если их имена не конфликтуют с GET-параметрами, которые могут использоваться в URL.

При использовании правил, приведённых выше, URL /index.php/post/123/create будет обработано как маршрут post/create с GET-параметром id=123. По маршруту comment/list с GET-параметром page=2, мы можем создать URL /index.php/comments?page=2.

5. Параметризация имён хостов

Также возможно использовать имена хостов в правилах для разбора и создания URL. Можно выделять часть имени хоста в GET-параметр. Например, URL http://admin.example.com/en/profile может быть разобран в GET-параметры user=admin и lang=en. С другой стороны, правила с именами хостов могут также использоваться для создания URL адресов.

Чтобы использовать параметризованные имена хостов, включите имя хоста в правила URL:

array(
    'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
)

Пример выше говорит, что первый сегмент имени хоста должен стать параметром user, а первый сегмент пути — параметром lang. Правило соответствует маршруту user/profile.

Помните, что CUrlManager::showScriptName не работает при создании URL адреса с использованием правил с параметризованным именем хоста.

Стоит отметить, что правило с параметризованным именем хоста не должно содержать поддиректорий в том случае, если приложение находится в поддиректории корня вебсервера. К примеру, если приложение располагается по адресу http://www.example.com/sandbox/blog, мы должны использовать точно такое же правило URL, как описано выше. Без поддиректории: sandbox/blog.

6. Скрываем index.php

С целью сделать адрес URL еще более привлекательным можно спрятать имя входного скрипта index.php. Для этого необходимо настроить веб-сервер и компонент приложения urlManager.

Вначале сконфигурируем веб-сервер таким образом, чтобы адрес URL без указания имени входного скрипта по-прежнему передавался на обработку входному скрипту. Для сервера Apache HTTP server это достигается путем включения механизма преобразования URL и заданием нескольких правил. Для этого необходимо создать файл /wwwroot/blog/.htaccess, содержащий правила, приведённые ниже. Те же правила могут быть размещены в файле конфигурации Apache в секции Directory для /wwwroot/blog.

RewriteEngine on

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php

Далее нужно установить свойство showScriptName компонента urlManager равным false.

Теперь, вызвав $this->createUrl('post/read',array('id'=>100)), мы получим URL /post/100. Что важно, этот адрес URL будет корректно распознан нашим веб-приложением.

7. Подмена окончания в адресе URL

В дополнение ко всему перечисленному выше, мы можем добавить к нашим адресам URL окончание. Например, мы можем получить /post/100.html вместо /post/100, представив пользователю как будто бы статичную страничку. Для этого нужно просто настроить компонент urlManager путем назначения свойству urlSuffix любого желаемого окончания.

8. Использование своего класса правила URL

Примечание: данная возможность доступна с версии 1.1.8.

По умолчанию каждое правило URL для CUrlManager представлено объектом класса CUrlRule. Этот объект разбирает запросы и создаёт URL по заданному правилу. Несмотря на то, что CUrlRule достаточно гибок и подходит для работы с большинством форматов URL, иногда требуются какие-либо особенные возможности.

К примеру, для сайта по продаже автомобилей может потребоваться поддерживать URL вида /Производитель/Модель, где и Производитель и Модель должны соответствовать данным из определённой таблицы базы данных. В этом случае класс CUrlRule не подойдёт так как он, в основном, работает с статически описанными регулярными выражениями, а не с базой данных.

В данном случае можно реализовать новый класс правила URL, унаследовав CBaseUrlRule, и использовать его в одном или нескольких правилах. Для приведённого выше примера с продажей автомобилей подойдут следующие правила URL:

array(
    // стандартное правило для обработки '/' как 'site/index'
    '' => 'site/index',
 
    // стандартное правило для обработки '/login' как 'site/login' и т.д.
    '<action:(login|logout|about)>' => 'site/<action>',
 
    // своё правило для URL вида '/Производитель/Модель'
    array(
        'class' => 'application.components.CarUrlRule',
        'connectionID' => 'db',
    ),
 
    // стандартное правило для обработки 'post/update' и др.
    '<controller:\w+>/<action:\w+>' => '<controller>/<action>',
),

Выше мы использовали свой класс правила URL CarUrlRule для обработки URL вида /Производитель/Модель. Данный класс может быть реализован следующим образом:

class CarUrlRule extends CBaseUrlRule
{
    public $connectionID = 'db';
 
    public function createUrl($manager,$route,$params,$ampersand)
    {
        if ($route==='car/index')
        {
            if (isset($params['manufacturer'], $params['model']))
                return $params['manufacturer'] . '/' . $params['model'];
            else if (isset($params['manufacturer']))
                return $params['manufacturer'];
        }
        return false;  // не применяем данное правило
    }
 
    public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
    {
        if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches))
        {
            // Проверяем $matches[1] и $matches[3] на предмет
            // соответствия производителю и модели в БД.
            // Если соответствуют, выставляем $_GET['manufacturer'] и/или $_GET['model']
            // и возвращаем строку с маршрутом 'car/index'.
        }
        return false;  // не применяем данное правило
    }
}

Свой класс правила URL должен реализовать два абстрактных метода, объявленных в CBaseUrlRule:

Кроме показанного выше типичного использования, свой класс URL может пригодиться и в других ситуациях. Например, можно реализовать класс правила, который будет записывать в журнал разбираемые и создаваемые URL. Это может пригодиться на этапе разработки. Также можно реализовать класс, который показывает особую страницу ошибки 404 в том случае, когда все остальные правила для разбираемого URL не сработали. Стоит отметить, что в этом случае правило с этим специальным классом должно указываться последним в списке.

Be the first person to leave a comment

Please to leave your comment.