Управление URL ============== Управление URL-адресами в веб-приложениях включает в себя два аспекта: 1. Приложению необходимо разобрать запрос пользователя, поступающий в виде URL, на отдельные параметры. 2. Приложение должно предоставлять способ формирования адресов URL, с которыми оно сможет корректно работать. В приложениях на Yii эти задачи решаются с использованием класса [CUrlManager]. > Note|Примечание: Вы можете не пользоваться Yii для генерации URL, однако, так делать не рекомендуется, потому как вы не сможете легко поменять URL приложения через конфигурацию без изменения кода. Создание адресов URL -------------------- В принципе, адреса URL можно задать прямо в коде представлений контроллера, однако куда удобнее создавать их динамически: ~~~ [php] $url=$this->createUrl($route,$params); ~~~ где `$this` относится к экземпляру контроллера; `$route` соответствует [маршруту](/doc/guide/basics.controller#route) запроса, а `$params` является списком параметров `GET` для добавления к URL. По умолчанию, адреса создаются посредством [createUrl|CController::createUrl] в `get`-формате. Например, при значениях параметров `$route='post/read'` и `$params=array('id'=>100)`, получим такой URL: ~~~ /index.php?r=post/read&id=100 ~~~ где параметры указаны в виде набора пар `имя=значение`, соединенных знаком `&`, а параметр `r` указывает на [маршрут](/doc/guide/basics.controller#route). Однако, этот формат не очень дружелюбен по отношению к пользователю. > Tip|Подсказка: Для того, чтобы сгенерировать 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|CWebApplication::urlManager] таким образом, чтобы метод [createUrl|CController::createUrl] мог автоматически переключиться на использование нового формата, а приложение могло корректно воспринимать новый формат адресов URL: ~~~ [php] array( … 'components'=>array( … 'urlManager'=>array( 'urlFormat'=>'path', ), ), ); ~~~ Обратите внимание, что указывать класс компонента [urlManager|CWebApplication::urlManager] не требуется, т.к. он уже объявлен как [CUrlManager] в [CWebApplication]. > Tip|Подсказка: Адрес URL, генерируемый методом [createUrl|CController::createUrl] является относительным. Для того, чтобы получить абсолютный адрес, нужно добавить префикс, используя `Yii::app()->request->hostInfo`, или вызвать метод [createAbsoluteUrl|CController::createAbsoluteUrl]. Человекопонятные URL -------------------- Если в качестве формата адреса URL используется `path`, то мы можем определить правила формирования URL, чтобы сделать адреса более привлекательными и понятными с точки зрения пользователя. Например, мы можем использовать короткий адрес `/post/100` вместо длинного варианта `/index.php/post/read/id/100`. [CUrlManager] использует правила формирования URL как для создания, так и для обработки адресов. Правила формирования URL задаются путем конфигурации свойства [rules|CUrlManager::rules] компонента приложения [urlManager|CWebApplication::urlManager]: ~~~ [php] array( … 'components'=>array( … 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( 'pattern1'=>'route1', 'pattern2'=>'route2', 'pattern3'=>'route3', ), ), ), ); ~~~ Правила задаются в виде массива пар шаблон-путь, где каждая пара соответствует одному правилу. Шаблон правила — строка, которая должна совпадать с путём в URL. Путь правила должен указывать на существующий [путь контроллера](/doc/guide/basics.controller#route). Кроме показанного выше способа задания правил, можно описать правило с указанием дополнительных параметров: ~~~ [php] 'pattern1'=>array('route1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false) ~~~ Начиная с версии 1.1.7, можно использовать показанный ниже формат. То есть паттерн указывается как элемент массива, что позволяет указать несколько правил одного паттерна: ~~~ [php] array('route1', 'pattern'=>'pattern1', 'urlSuffix'=>'.xml', 'caseSensitive'=>false) ~~~ Здесь массив содержит список дополнительных параметров для правила. Возможно указать следующие параметры: - [pattern|CUrlRule::pattern]: паттерн, который будет использован при сопоставлении и создании URL. Данная возможность доступна с версии 1.1.7. - [urlSuffix|CUrlRule::urlSuffix]: суффикс URL, используемый исключительно для данного правила. По умолчанию равен null, что означает использование значения [CUrlManager::urlSuffix]. - [caseSensitive|CUrlRule::caseSensitive]: учитывает ли правило регистр. По умолчанию параметр равен null, что означает использование значения [CUrlManager::caseSensitive]. - [defaultParams|CUrlRule::defaultParams]: GET-параметры по умолчанию (`имя=>значение`) для данного правила. При срабатывании правила параметры будут добавлены в `$_GET`. - [matchValue|CUrlRule::matchValue]: должны ли значения GET-параметров при создании URL совпадать с соответствующими подвыражениями в основном правиле. По умолчанию параметр равен null, что означает использование значения [CUrlManager::matchValue]. При значении параметра false правило будет использовано для создания URL только если имена параметров совпадают с именами в правиле. При значении true значения параметров дополнительно должны совпадать с подвыражениями в правиле. Стоит отметить, что установка значения в true снижает производительность. - [verb|CUrlRule::verb]: тип HTTP запроса (например, `GET`, `POST`, `DELETE`), для которого работает данное правило. По умолчанию равен null, что означает работу правила с любыми HTTP запросами. Если необходимо указать несколько типов запросов, их надо разделить запятыми. В том случае, когда правило не совпадает с текущим типом запроса, оно пропускается на этапе разбора запроса. Данная опция используется только для разбора запроса и введена для поддержки URL в стиле REST. Данная возможность доступна с версии 1.1.7. - [parsingOnly|CUrlRule::parsingOnly]: использовать ли правило только на этапе разбора запроса. По умолчанию параметр равен false, что означает, что правило используется как для разбора запроса, так и для построения URL. Данная возможность доступна с версии 1.1.7. Использование именованных параметров ------------------------------------ Правило может быть ассоциировано с несколькими GET-параметрами. Эти параметры указываются в шаблоне правила в виде маркеров следующим образом: ~~~ ~~~ где `ParamName` соответствует имени GET-параметра, а необязательный `ParamPattern` — регулярному выражению, которое используется для проверки соответствия значению GET-параметра. Если `ParamPattern` не указан, то параметр должен соответствовать любым символам, кроме слэша `/`. В момент создания URL маркеры будут заменены на соответствующие значения параметров, а в момент обработки URL, соответствующим GET-параметрам будут присвоены результаты обработки. Для наглядности приведем несколько примеров. Предположим, что наш набор правил состоит из трех правил: ~~~ [php] array( 'posts'=>'post/list', 'post/'=>'post/read', 'post//'=>'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|CController::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`). > Note|Примечание: Использование правил URL снижает производительность приложения. Это происходит по той причине, что в процессе парсинга запрошенного URL [CUrlManager] пытается найти соответствие каждому правилу до тех пор, пока какое-нибудь из правил не будет применено. Чем больше правил, тем больший урон производительности. Поэтому в случае высоконагруженных приложений использование правил URL стоит минимизировать. Параметризация маршрутов ------------------------ Мы можем использовать именованные параметры в маршруте правила. Такое правило может быть применено к нескольким маршрутам, совпадающим с правилом. Это может помочь уменьшить число правил и, таким образом, повысить производительность приложения. Для того, чтобы показать параметризацию маршрутов, используем следующий набор правил: ~~~ [php] 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`. Параметризация имён хостов -------------------------- Также возможно использовать имена хостов в правилах для разбора и создания URL. Можно выделять часть имени хоста в GET-параметр. Например, URL `http://admin.example.com/en/profile` может быть разобран в GET-параметры `user=admin` и `lang=en`. С другой стороны, правила с именами хостов могут также использоваться для создания URL адресов. Чтобы использовать параметризованные имена хостов, включите имя хоста в правила URL: ~~~ [php] 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`. Скрываем `index.php` -------------------- С целью сделать адрес URL еще более привлекательным можно спрятать имя входного скрипта `index.php`. Для этого необходимо настроить веб-сервер и компонент приложения [urlManager|CWebApplication::urlManager]. Вначале сконфигурируем веб-сервер таким образом, чтобы адрес URL без указания имени входного скрипта по-прежнему передавался на обработку входному скрипту. Для сервера [Apache HTTP server](http://httpd.apache.org/) это достигается путем включения механизма преобразования 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|CUrlManager::showScriptName] компонента [urlManager|CWebApplication::urlManager] равным `false`. Теперь, вызвав `$this->createUrl('post/read',array('id'=>100))`, мы получим URL `/post/100`. Что важно, этот адрес URL будет корректно распознан нашим веб-приложением. Подмена окончания в адресе URL ------------------------------ В дополнение ко всему перечисленному выше, мы можем добавить к нашим адресам URL окончание. Например, мы можем получить `/post/100.html` вместо `/post/100`, представив пользователю как будто бы статичную страничку. Для этого нужно просто настроить компонент [urlManager|CWebApplication::urlManager] путем назначения свойству [urlSuffix|CUrlManager::urlSuffix] любого желаемого окончания. Использование своего класса правила URL --------------------------------------- > Note|Примечание: данная возможность доступна с версии 1.1.8. По умолчанию каждое правило URL для [CUrlManager] представлено объектом класса [CUrlRule]. Этот объект разбирает запросы и создаёт URL по заданному правилу. Несмотря на то, что [CUrlRule] достаточно гибок и подходит для работы с большинством форматов URL, иногда требуются какие-либо особенные возможности. К примеру, для сайта по продаже автомобилей может потребоваться поддерживать URL вида `/Производитель/Модель`, где и `Производитель` и `Модель` должны соответствовать данным из определённой таблицы базы данных. В этом случае класс [CUrlRule] не подойдёт так как он, в основном, работает с статически описанными регулярными выражениями, а не с базой данных. В данном случае можно реализовать новый класс правила URL, унаследовав [CBaseUrlRule], и использовать его в одном или нескольких правилах. Для приведённого выше примера с продажей автомобилей подойдут следующие правила URL: ~~~ [php] 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 вида `/Производитель/Модель`. Данный класс может быть реализован следующим образом: ~~~ [php] 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]: * [createUrl()|CBaseUrlRule::createUrl()] * [parseUrl()|CBaseUrlRule::parseUrl()] Кроме показанного выше типичного использования, свой класс URL может пригодиться и в других ситуациях. Например, можно реализовать класс правила, который будет записывать в журнал разбираемые и создаваемые URL. Это может пригодиться на этапе разработки. Также можно реализовать класс, который показывает особую страницу ошибки 404 в том случае, когда все остальные правила для разбираемого URL не сработали. Стоит отметить, что в этом случае правило с этим специальным классом должно указываться последним в списке.