Маршрутизация во фреймворках: CUrlManager в Yii
В первой части мы с вами рассмотрели работу системы маршрутизации, изучили некоторые нюансы и научились правильно составлять шаблоны адресов. В этой же части мы попробуем перейти непосредственно к пониманию работы и нюансам использования менеджера адресов в Yii Framework.
Первая часть: Маршрутизация во фреймворках: Управление адресами URL
Маршрутизация в Yii Framework
Читателя, освоившего первую часть, можно поздравить с тем, что он теперь знает принцип работы маршрутизации в Yii. Да-да, достаточно немного улучшить «придуманный» нами код, и мы получим классы CUrlManager и CUrlRule этого фреймворка.
Компонент Yii::app()->urlManager
по умолчанию является экземпляром класса CUrlManager
и описывается как и другие компоненты в конфигурационном файле. После создания приложения командой yiic webapp
секция этого компонента в конфигурационном файле имеет вид:
return array( ... 'components'=>array( ... 'urlManager'=>array( 'urlFormat' => 'path', 'rules'=>array( '<controller:\w+>/<id:\d+>' => '<controller>/view', '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>', '<controller:\w+>/<action:\w+>' => '<controller>/<action>', ), ), ... ), );
в параметре rules
уже имеется несколько удобных правил для маршрутов контроллер/действие
. Это, по сути, те же правила по умолчанию, которые мы получали ранее.
Если вообще убрать имеющиеся правила, то маршрут post/view
подставится в первозданном виде и параметр id
просто передастся в GET строке:
http://site.com/index.php/post/view?id=52
А если не убирать, то сгенерированный адрес для маршрута post/view
в соответствие с первым правилом будет выглядеть так:
http://site.com/index.php/post/52
Если же поменять значение 'urlFormat'=>'path'
на 'urlFormat'=>'get'
, то адресация будет производиться простыми GET параметрами:
http://site.com/index.php?r=post&id=52
Для передачи маршрута в обычном GET запросе в Yii по умолчанию используется параметр r
.
Настройка человекопонятных адресов в Yii
Чтобы наиболее эффективно воспользоваться преобразованиями адресов мы выберем вариант с 'urlFormat'=>'path'
, но уберём index.php
из адреса.
Сначала нужно настроить соответствующим образом наш сервер на перенаправление всех запросов в файл index.php
, как мы делали это вначале:
Конфигурация Apache (.htaccess):
RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php
Конфигурация Nginx:
server { ... location / { index index.html index.php; try_files $uri $uri/ /index.php?$args; } }
Теперь можем убрать имя файла index.php
из адреса, указав 'showScriptName'=>false
:
'urlManager'=>array( 'urlFormat' => 'path', 'showScriptName'=>false, 'urlSuffix' => '', 'rules'=>array( 'feed.xml' => array('post/feed', 'urlSuffix' => ''), '<controller:\w+>/<id:\d+>' => '<controller>/view', '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>', '<controller:\w+>/<action:\w+>' => '<controller>/<action>', ), ),
Теперь если мы зайдём по адресу
http://site.com/post/update/1
то согласно третьему правилу выполнится действие post/update
и GET-параметру id
присвоится значение 1
. Для удобства в Yii сделано так, что параметры для подстановки можно указывать как аргументы метода действия:
class PostController extends Controller { public function actionUpdate($id) { echo $id; // выведет '1' } }
Заодно мы попробовали добавить правило для обработки feed.xml
:
'feed.xml' => array('post/feed', 'urlSuffix' => ''),
Почему же мы сделали это таким способом? Мы же могли это сделать так:
'feed.xml' => 'post/feed',
Параметр urlSuffix
, который мы уже упоминали, позволяет задать дополнение к адресу. Но этот параметр есть как у отдельных правил, так и у самого менеджера.
Для всего менеджера можно указать, например, значение .html
, и всюду добавится этот суффикс. Например, страница авторизации и запись блога будет открываться теперь по адресу:
http://site.com/site/login.html http://site.com/post/52.html
А теперь представим, что мы вписали в шаблон правила непосредственно feed.xml
и захотели добавить глобальный суффикс .html
:
'urlManager'=>array( ... 'urlSuffix' => '.html', 'rules'=>array( 'feed.xml' => 'post/feed', ), ),
Не обнули мы суффикс для нашего правила, система сгенерировала бы адрес:
http://site.com/feed.xml.html
что не очень хорошо.
Так что эти способы правильны и равнозначны:
'feed.xml' => array('post/feed', 'urlSuffix' => ''), 'feed' => array('post/feed', 'urlSuffix' => '.xml'), array('template' => 'feed', 'route' => 'post/feed', 'urlSuffix' => '.xml'), array('template' => 'feed.xml', 'route' => 'post/feed', 'urlSuffix' => ''),
Чтобы не было потенциальных неприятностей с двойными суффиксами, обнуляйте вручную суффиксы специфических правил.
Дабы посмотреть все возможные настройки откройте файлы CUrlManager
и CUrlRule
. Посмотрите имеющиеся у них публичные поля. Любое из них можно настраивать подобным образом. Некоторые параметры мы рассмотрим ниже.
В любом случае, в правиле должен быть указан целевой маршрут в формате контроллер/действие
или модуль/контроллер/действие
и шаблон в формате, к которому мы пришли в первой части.
Расположение методов createUrl и createAbsoluteUrl
Как видно из настроек компонента, менеджер адресов доступен в приложение как компонент
Yii::app()->urlManager
Метод parseUrl
нам вряд ли понадобится вызывать. Его приложение вызывает само для определения того, какое действие какого контроллера нужно выполнить. Метод createUrl
есть как в самом менеджере, так и в классах правил. Методы правил мы вручную не вызываем (этим занимается менеджер, обходя по очереди все правила). Соответственно, нам понадобится только метод createUrl
менеджера маршрутизации.
Его мы можем вызвать прямо у компонента парой способов:
echo Yii::app()->urlManager->createUrl('post/feed'); echo Yii::app()->getUrlManager()->createUrl('post/feed');
Для удобства использования аналогичный вспомогательный метод есть в классе самого приложения:
class CApplication { ... public function createUrl($route, $params=array(), $ampersand='&') { return $this->getUrlManager()->createUrl($route, $params, $ampersand); } ... }
Он позволяет записывать обращение короче:
<a href="<?php echo Yii::app()->createUrl('post/feed'); ?>">RSS</a>
Это правило сгенерирует относительный адрес от корня сайта:
<a href="/feed.xml">RSS</a>
По соседству в менеджере присутствует метод createAbsoluteUrl
, который достраивает адрес до абсолютного:
<a href="<?php echo Yii::app()->createAbsoluteUrl('post/feed'); ?>">RSS</a>
Это правило сгенерирует абсолютный адрес:
<a href="http://site.com/feed.xml">RSS</a>
Указание полного адреса бывает полезно для построения карты сайта, адресов в RSS и для встраивания кнопок «Мне нравится» социальных сетей.
Третий способ немного отличается от первых двух. Его мы рассмотрим подробнее.
Метод createUrl контроллера
Третьим способом генерации ссылок является вызов одноимённого вспомогательного метода в контроллере:
class CController { ... public function createUrl($route,$params=array(),$ampersand='&') { if($route==='') $route=$this->getId().'/'.$this->getAction()->getId(); elseif(strpos($route,'/')===false) $route=$this->getId().'/'.$route; if($route[0]!=='/' && ($module=$this->getModule())!==null) $route=$module->getId().'/'.$route; return Yii::app()->createUrl(trim($route,'/'),$params,$ampersand); } }
Пока не будем обращать внимания на его содержимое.
Его мы можем вызывать от от контроллера, то есть от указателя $this
в контроллере или в представлении:
<a href="<?php echo $this->createUrl('/post/feed'); ?>">RSS</a>
В других компонентах (например, в виджетах) нужно сначала получить сам контроллер, который хранится в Yii::app()->controller
, и вызвать метод у него:
<a href="<?php echo Yii::app()->controller->createUrl('/post/feed'); ?>">RSS</a>
или воспользоваться вызовом Yii::app()->createUrl(...)
.
Теперь рассмотрим назначение добавленного в метод CController::createUrl
функционала.
Если ваше приложение использует модули, то можно указать маршруты по умолчанию для них:
'urlManager'=>array( 'urlFormat' => 'path', 'showScriptName'=>false, 'urlSuffix' => '', 'rules'=>array( ''=>array('site/index', 'urlSuffix' => ''), '<action:login|logout|register>' => 'site/<action>', 'feed.xml'=>array('blog/post/feed', 'urlSuffix' => ''), '<module:\w+>/<controller:\w+>/<id:\d+>' => '<module>/<controller>/view', '<module:\w+>/<controller:\w+>/<action:\w+>/<id:\d+>' => '<module>/<controller>/<action>', '<module:\w+>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>', ), ),
Обратите внимание на первое правило. По умолчанию при входе на главную страницу сайта сработает действие по умолчанию
index
контроллера, указанного в параметреdefaultController
приложения (по умолчанию этоsite
). Чтобы изменить домашнюю страницу сайта можно либо указать другое значение параметруdefaultController
в конфигурациооном файле, либо добавить указанное первое правило и указать там свой маршрут.
Метод Yii::app()->createUrl
всегда генерирует независимую ссылку по переданному ему маршруту.
Представим, что мы используем модули и у нас есть модуль blog
.
Тогда вызов:
echo Yii::app()->createUrl('blog/post/update', array('id'=>1));
сгенерирует ссылку от корневого адреса приложения:
/blog/post/update/1
А метод $this->createUrl(...)
находится в контроллере и обучен производить относительную адресацию от текущего модуля, контроллера и действия.
Если вы находитесь в blog/post/index
, то, например, запись:
echo $this->createUrl('blog/post/update', array('id'=>1));
сгенерирует то же самое:
/blog/post/update/1
Теперь попробуем поэкспериментировать, убрав часть blog/
и оставив только post/update
:
echo $this->createUrl('post/update', array('id'=>1));
Даже в этом случае мы получим такой же адрес.
Продолжим эксперимент:
echo $this->createUrl('update', array('id'=>1));
И сейчас адрес не изменился, хотя мы и оставили только имя действия.
Но поробуем запустить этот же код в действии actionIndex
контроллера ProductController
модуля ShopModule
:
echo $this->createUrl('update', array('id'=>1)) . PHP_EOL; // (1) echo $this->createUrl('post/update', array('id'=>1)) . PHP_EOL; // (2) echo $this->createUrl('blog/post/update', array('id'=>1)) . PHP_EOL; // (3) echo $this->createUrl('', array('id'=>1)) . PHP_EOL; // (4) echo $this->createUrl('site/login') . PHP_EOL; // (5) echo $this->createUrl('/site/login') . PHP_EOL; // (6)
Вот такие адреса мы увидим на экране:
/shop/product/update/1 /shop/post/update/1 /blog/post/update/1 /blog/product/index/1 /shop/site/login /login
Метод CController::createUrl
генерирует ссылку относительно текущего действия. Случай (1) сохранил текущий контроллер и модуль и построил новый адрес /shop/product/update/1
(вроде относительной адресации в консоли cd ../update
). Случай (2) поднялся от текущего действия на два уровня вверх (как cd ../../post/update
) и сохранил только текущий модуль. Соответственно, пустой $this->createUrl('')
в случае (3) по принципам относительной адресации сгенерирует ссылку на тот же маршрут shop/product/index
.
Относительная маршрутизация удобна для упрощения копировании кода CRUD одного модуля в другой, так как в маршрутах можно не указывать имя модуля. Вызов
$this->createUrl('index')
будет правильно ссылаться наactionIndex
того контроллера, в котором он производится. Кнопки CButtonColumn виджета CGridView, например, используют контекст контроллера:Yii::app()->controller->createUrl('update', array('id'=>$data->primaryKey));
Это позволяет им по умолчанию ссылаться на действие
actionUpdate
в любом текущем контроллере любого модуля.
Соответственно, нужно быть осторожным при использовании $this
, так как относительная от текущего модуля ссылка $this->createUrl('site/login')
в случае (5) может вас из модуля магазина нечаянно завести на несуществующий адрес /shop/site/login
. Так что ссылаясь из одного модуля в другой нужно либо использовать беспристрастный Yii::app()->createUrl('site/login')
, либо не забыть заставить маршрутизатор генерировать адрес от корня, добавив слэш вначале: $this->createUrl('/site/login')
. Как в случае (6) вкупе со вторым правилом маршрутизации:
'<action:login|logout|register>' => 'site/<action>',
Помошник CHtml::link
Для генерации той же ссылки на действие редактирования записи post/update
из действия post/admin
можно воспользоваться как непосредственным вызовом метода createUrl
:
<a href="<?php echo Yii::app()->createUrl('post/update', array('id'=>1)); ?>">Редактировать</a> <a href="<?php echo $this->createUrl('update', array('id'=>1)); ?>">Редактировать</a>
Так и воспользовавшись помощником CHtml::link
:
<?php echo CHtml::link('Редактировать', $this->createUrl('update', array('id'=>1))); echo CHtml::link('Редактировать', array('update', 'id'=>1));
В первой строке мы сами сгенерировали адрес и передали этому методу для построения тега <a href="...">...</a>
, а во второй мы передали всё необходимое для построения адреса самому помошнику. Это можно сделать в форме массива:
<?php echo CHtml::link('Редактировать', array('update', 'id'=>$model->id)); echo CHtml::link('Все записи', array('index'));
Рассмотрим код меню из шаблона официального демо-блога:
<div id="mainmenu"> <?php $this->widget('zii.widgets.CMenu',array( 'items'=>array( array('label'=>'Home', 'url'=>array('post/index')), array('label'=>'About', 'url'=>array('site/page', 'view'=>'about')), array('label'=>'Contact', 'url'=>array('site/contact')), ... ), )); </div><!-- mainmenu -->
Мы можем заметить, что адреса элементов для виджетов CMenu
(и CBreadcrumbs
тоже) передаются аналогично в виде массива. Внутри этих виджетов для построения ссылок вызывается тот же помошник CHtml::link
.
Если изучить исходный код метода
CHtml::link
, то можно увидеть, что по умолчанию он использует контекст контроллера. Если ему вместо адреса передаётся массивarray('site/page', 'view'=>'about')
то он извлекает первый элемент и использует его как маршрут при вызове метода
createUrl
контроллера:Yii::app()->getController()->createUrl('site/page', array('view'=>'about'))
Способы построения адресов и ссылок мы рассмотрели. Перейдём к обзору некоторых дополнительный возможностей.
Дополнительные параметры маршрутизатора CUrlManager
Дополнительные опции маршрутизатора можно найти в самом классе CUrlManager
. Рассмотрим здесь некоторые из них.
Предположим, что мы сделали наше приложение и прописали правила маршрутизации. Нас сразу же поджидают некоторые неприятности.
Во-первых, мы можем прописывать в адресной строке любой регистр:
http://site.com/blog/post/1 http://site.com/BLOg/POst/1
На это можно и не обращать внимания, но можно включить строгую слежку за регистром, добавив в параметры компонента urlManager
значение 'caseSensitive'=>true
.
Но это не самое страшное. Как мы уже говорили, есть несколько способов открыть один и тот же контроллер. Но даже если мы добавили свои специфические правила маршрутизации и скрыли имя файла, то всё равно все способы продолжают работать. То есть запись блога можно прочитать несколькими путями, например:
http://site.com/blog/post/1 http://site.com/blog/post/view?id=1 http://site.com/index.php/blog/post/1 http://site.com/index.php?r=/blog/post/1 http://site.com/index.php?r=/blog/post/view?id=1
Также главную страницу сайта можно открыть несколькими способами:
http://site.com/ http://site.com/site http://site.com/site/index http://site.com/index.php/site http://site.com/index.php/site/index http://site.com/index.php?r=/site http://site.com/index.php?r=/site/index
Конечно же на самом сайте таких ссылок не будет, так как они у нас будут генерироваться по правилам, но наш сосед (который знает, что мы сделали сайт на Yii) может нарочно надобавлять десятки таких ссылок в свои сообщения на форумах и в Twitter. В итоге в индексе поисковых систем вместо ста страниц сайта будут тысячи копий одних и тех же текстов, что может расцениваться поисковиками как спам.
Чтобы избежать дублирования контента можно заставить менеджер маршрутизации работать только по правилам опцией 'useStrictParsing'=>true
.
Если у нас есть правила по умолчанию в таком виде:
'<module:\w+>' => '<module>/default/index', '<module:\w+>/<controller:\w+>' => '<module>/<controller>/index', '<module:\w+>/<controller:\w+>/<id:\d+>' => '<module>/<controller>/view', '<module:\w+>/<controller:\w+>/<action:\w+>/<id:\d+>' => '<module>/<controller>/<action>', '<module:\w+>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>',
То теперь главная страница блога может открываться только тремя вариантами по первому, второму и пятому правилу:
http://site.com/blog http://site.com/blog/default http://site.com/blog/default/index
Чтобы избежать такого дублирования следует отказаться от правил по умолчанию вовсе и прописывать все возможные варианты вручную:
'blog' => 'blog/default/index', 'blog/category/<id:\d+>' => 'blog/default/category', 'blog/tag/<tag:[\w-]+>' => 'blog/default/tag', 'blog/<action:update|delete>/<id:\d+>' => 'blog/post/<action>', 'blog/<action:create>' => 'blog/post/<action>', 'blog/<id:\d+>' => 'blog/post/view',
Только в этом случае блог, его категорию, метку и запись можно будет открывать только одним способом. Все остальные варианты будут выдавать ошибку.
Итак, чтобы избежать дублирования страниц необходимо прописать все возможные варианты адресов вручную и установить 'useStrictParsing'=>true
. Может получиться так:
'urlManager'=>array( 'urlFormat' => 'path', 'showScriptName' => false, 'useStrictParsing' => true, 'caseSensitive' => true, 'urlSuffix' => '', 'rules'=>array( ... ), ),
Прописывание всех правил и включение
useStrictParsing
– это надёжный, но не единственный вариант. Можно пойти весьма элегантным путём, перенаправляя пользователя со всех неправильных адресов на единственный правильный:ksort($_GET); $url = $this->createUrl('', $_GET); if (Yii::app()->request->getUrl() != $url) { $this->redirect($url, 301); }
Здесь мы собираем правильный адрес (он будет собираться всегда одинаково) и сравниваем его с текущим. Если адреса не совпадают, то делаем редирект на наш правильный.
Дополнительные параметры правила CUrlRule
Из первой части мы помним, что правила можно задавать несколькими способами:
'feed.xml' => 'post/feed', 'feed' => array('post/feed', 'urlSuffix' => '.xml'), array('template' => 'feed', 'route' => 'post/feed', 'urlSuffix' => '.xml'),
При обработке этих правил менеджер для каждого создаёт экземпляр класса CUrlRule и присваивает его открытым полям urlSuffix
, caseSensitive
, defaultParams
, matchValue
, verb
, parsingOnly
указанные значения.
Мы уже ознакомились с параметрами urlSuffix
и caseSensitive
менеджера. Внутри правила они работают таким же образом.
Ещё из полей класса CUrlRule
в специфических задачах порой используют параметр verb
. Он служит для указания метода HTTP-запроса, с которым это правило должно работать.
Это применимо для приобретающей популярность архитектуры REST. Суть её в том, что вместо повсеместного использования форм с method="post"
c передачей данных на сервер методом POST воскресают и используются по их исконному прямому назначению все методы GET, PUT, DELETE и POST. При этом они могут ссылаться на один и тот же адрес, но сервером должны обрабатываться в зависимости от типа запроса.
Соответственно, для реализации REST-архитектуры необходимо добавить группу правил для каждого типа запроса:
array('template' => 'blog/posts', 'route' => 'blog/default/index', 'verb' => 'GET'), array('template' => 'blog/posts/<id:\d+>', 'route' => 'blog/post/view', 'verb' => 'GET'), array('template' => 'blog/posts/<id:\d+>', 'route' => 'blog/post/update', 'verb' => 'PUT'), array('template' => 'blog/posts/<id:\d+>', 'route' => 'blog/post/delete', 'verb' => 'DELETE'), array('template' => 'blog/posts', 'route' => 'blog/post/create', 'verb' => 'POST'),
Теперь в зависимости от метода сработает подходящее ему правило. При необходимости чтобы не копировать одно и то же правило мы можем указывать несколько типов запроса через запятую: 'verb'=>'GET,POST'
.
Другие опции используются реже.
Создание своих классов правил
В первой части мы создавали своё правило для маршрутизации статических страниц:
http://site.com/about http://site.com/services http://site.com/delivery-information
в виде своего класса. Аналогично мы можем сделать и в Yii. Немного изменятся лишь сигнатуры методов и работа с базой данных:
class PageUrlRule extends CBaseUrlRule { public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) { if (preg_match('#^([\w-]+)#i', $pathInfo, $matches)) { $page = Page::model()->find(array( 'condition' => 'alias = :alias', 'params' => array('alias'=>$matches[1]), )); if ($page !== null) { $_GET['id'] = $page->getPrimaryKey(); return 'page/default/view'; } } return false; } public function createUrl($manager, $route, $params, $ampersand) { if ($route == 'page/default/view') { if (!empty($params['id'])) { if ($page = Page::model()->findByPk($params['id'])) { return $page->alias; } } } return false; } }
Теперь добавим наше правило с указанием класса перед правилами по умолчанию:
... array('class'=>'PageUrlRule'), '<module:\w+>' => '<module>/default/index',
Это правило сработает только для существующих страницы /about
и пропустит мимо себя запрос модуля /blog
.
При создании правила мы отнаследовались от класса
CBaseUrlRule
вместоCUrlRule
, поэтому никакие опции вродеurlSuffix
иverb
у в нашем правиле работать не будут, так как их в нашем классе нет. При желании мы можем самостоятельно добавить любые параметры в свой класс:class PageUrlRule extends CBaseUrlRule { public $urlSuffix = ''; public $verb = ''; .... }
и реализовать их поддержку вручную в методах
parseUrl
иcreateUrl
. Например, еслиurlSuffix
не указан, то его по умолчанию можно взять равным значениюYii::app()->urlManager->urlSuffix
.
Работа с поддоменами
В шаблонах правил маршрутизации именованные параметры можно использовать в каком угодно месте. Даже при необходимости можно указывать их на месте поддомена сайта. Это может пригодиться, например, если вы захотите разместить профили пользователей на поддоменах:
'http://<username:\w+>.site.com/profile' => 'user/profile',
Так сделано на многих блог-платформах, например LiveJournal.com. Достаточно лишь настроить сервер так, чтобы запросы ко всем поддоменам ссылались на одну директорию с нашим файлом index.php
на сервере.
Но как быть с остальными правилами? Ведь если мы перейдём на поддомен пользователя, то с нашими правилами:
''=>array('site/index', 'urlSuffix' => ''), '<action:login|logout|register>' => 'site/<action>', 'blog' => 'blog/default/index', 'blog/<action:update|delete>/<id:\d+>' => 'blog/post/<action>', 'blog/<action:create>' => 'blog/post/<action>', 'blog/<id:\d+>' => 'blog/post/view', 'http://<username:\w+>.site.com/profile' => 'user/profile',
мы сможем прочитать блог этого пользователя, но не сможем вернуться обратно на главный поддомен, например на страницу http://www.site.com/login
. Чтобы это было возможно нам необходимо будет явно указать основной поддомен www.site.com
у необходимых правил:
'http://www.site.com/' => 'site/index', 'http://www.site.com/<action:login|logout|register>' => 'site/<action>', 'blog' => 'blog/default/index', 'blog/<action:update|delete>/<id:\d+>' => 'blog/post/<action>', 'blog/<action:create>' => 'blog/post/<action>', 'blog/<id:\d+>' => 'blog/post/view', 'http://<username:\w+>.site.com/profile' => 'user/profile',
Теперь на каком бы поддомене мы ни находились, пункт меню
<a href="<?php echo Yii::app()->createUrl('site/register'); ?>">Регистрация</a>
всегда будет вести на основной сайт:
<a href="http://www.site.com/register">Регистрация</a>
Ресурсы по теме
Здесь мы рассмотрели только основные моменты работы с маршрутами в Yii. Дополнительную информацию можно получить из статьи Управление URL официального руководства Yii.
Также могут быть полезными другие материалы на этом сайте:
- Генерация URL для вложенных категорий (со слэшами)
- Загрузка правил маршрутизации из модулей
- Беседа о маршрутизации в Yii и Yii2
В любом случае эта тема достаточно обширная. Многие общие и специфические моменты также описаны в книгах по Yii Framework.
Спасибо за подробную статью. Как всегда все правильно и доступно написано. С удовольствием читаю Ваши статьи.
Помогите со следующей проблемой. Есть таблица Type (типы сущностей: новости, статьи и т.д.) и таблица Objects (материалы). Хочу сделать урл такого вида: type_slug/id/slug. Т.е, чтобы выборка шла от типа материала. Тут нужно свое правило наверное. Спасибо.
Чтобы не было конфликтов лучше сделать своё правило-класс.
Ты походу шаришь в yii чпу.
Подскажи по моему вопросу:
http://www.yiiframework.ru/forum/viewtopic.php?f=19&t=14900&start=10#p92924
Цель - полный контроль контент менеджера над ЧПУ синонимами.
Т.е. не только над хвостовиками типа как со slug, а чтобы от корня синоним задавался.
Аналогично PageUrlRule создаёте свой супер-преобразователь адреса в контроллер-действие и подключаете только его:
А он уже по вашей таблице соответствий или ещё как будет извлекать имя нужного контроллера и действия.
Так я же про это и писал что я создаю свое правило.
Ваше PageUrlRule написано только для одного пути. И вы знаете что для него нужен параметр 'id'.
А меня интересует как для всех страниц сайта такое сделать.
А насчет таблицы соответствий или еще как - вот в том то и вопрос, что нет никакой таблицы соответствий, брать эту инфу неоткуда.
Скачал я модуль слайдера например.
Есть у него путь
Как я накачу на этот путь синоним через мою общую таблицу синонимов?
Создается путь так:
Внутри UrlRule::createUrl я не буду знать по какому запросу искать синоним. Вы то в вашем примере знаете что по 'id'
Переименуйте $idslider в $id в этом и всех других действиях.
Таблицу синонимов сделайте так:
Тогда у вас тоже будет поле 'id' для всех действий.
Остальные параметры конкатенируйте через '?' и разбирайте с parse_str().
Во первых необходимых параметром может быть не только один, а и 2, или 3,....
Во вторых если бы я их все мог попереименовывать, то вопроса бы не стояло.
Но:
1) Вы что мне предлагаете, для любого модуля который я поставил с package.org сидеть и переименовывать параметры?
2) Параметр может быть и $type к примеру и вместо него использовать $id - некрасиво по коду.
Для определения обязательных параметров используйте рефлексию, а в таблице поле id переделайте на хранение сериализованного массива основных параметров на её основе:
Потом по адресу находите строку, десереализуйте массив параметров и примешивайте в $_GET.
К сожалению рефлексия не подходит.
Получится что при просто создании ссылки мне придется загрузить класс (аутолоад). Это очень накладно. Ссылок то могут быть сотни на странице.
да и к тому же этот класс с нужным действием надо найти по роуту (slider/view). По сути все то что при поиске контроллера и действия при разборе запроса.
Ну просчитайте параметры у всех контроллеров один раз и закэшируйте.
Ну вот и я о том же. Из-за так сделанного роутинга, проблема ЧПУ превращается в пытку прям какую то, со спорными алгоритмами типа "Пройти по всем модулям и их контроллерам, а в папке контроллера еще в подпапках контроллеры..", и пошло поехало. И потом это все в память грузить.
Ну поищите другую систему, где это есть. Посмотрите как там сделано.
Да ну не шутите так. Другую систему. Я в yii столько времени инвестировал. Про эту проблему я случайно узнал, даже как то не думал что такое может быть. В друпал все правильно сделано. В кохане глянул - вроде тоже правильно.
Тут все из-за того что роут состоит только из контроллера/действия.
Странно, что никто этого раньше не замечал.
Даже вон по вашему примеру с PageUrlRule очевидна эта проблема:
Если такое управление адресом ОТ КОРНЯ будет требоваться для разных роутов то придется создавать свои правила типа - PageUrlRule - NewsUrlRule , UsersUrlRule и т.д.
И если эти УРЛ правила будут искать , как у вас сделано, каждый в своей таблице, то не факт же что первое правило найдет соответствие. Станет работать второе, третье...
А это все множество излишних запросов к базе.
Выход один - одна таблица для синонимов для всего сайта.
Распарсить запрос становиться легко.
А для создания ссылок уже и приходим к моей проблеме.
Ну вот и перепишите сам urlManager так, чтобы он как в Cohana работал.
urlManager не отвечает за роутинг. Он не передает данные в контроллер, а только парсит их туда-сюда.
Да и переписать его полностью нельзя, т.к. если синонима нет, то он же должен уметь работать с теми путями что по умолчанию.
Здравствуйте!
Спасибо большое за статью, но хотел задать следующий вопрос: есть ли возможность (и если есть, то как) узнать на какой модуль/контроллер/действие ссылается конкретный url?
Если он есть в правилах, то можно посмотреть там, но что, если его нет там?
Приведу простой пример:
Есть модуль admin. У него главный контроллер и действие index/index. При этом роут на /admin не описан в правилах. Можно ли как-нибудь из когда узнать, что роут /admin ссылается на модуль admin, котроллер index, экшн index?
Можно попробовать вызвать CUrlManager::parseUrl.
Да, я смотрел уже в сторону этого метода, но он принимает CHttpRequest в качестве единственного параметра.
Затем (на сколько я понял) смотрит на pathInfo, если urlFormat - path. Дальше бежит по правилам, ищет подходящее. Свойство pathInfo у CHttpRequest доступно только для чтения. Хотя, можно попробовать и самому вручную пробежаться по правилам.
Подскажите пожалуйста, ставлю 'useStrictParsing'=>true и перестает работать модуль админки.
Например /backend/category работает (index), а /backend/category/create уже нет
Добавьте правила по умолчанию только для модуля backend:
и может подскажите как правильно сделать ссылку в таком виде /category/news
и чтобы обрабатывало как контроллер=post экшин=show id = news
Здравствуйте Дмитрий! Проконсультируйте пожалуйста, вопрос у меня следующий, как мне организовать проверку вводимого адреса? Т.е. валидацию надо сделать, да, только незнаю как лучше.
Адреса могут быть следующие:
- стандартные маршруты
- /урл_страницы (урл_категория)
- /урл_категория/урл_категория/урл_страницы (вложенные)
- внешний адрес (http://address)
- ссылка на файл.
Регулярными выражениями, например. А потом при совпадении проверяем правильность путей:
В любом случае оформить это как класс-валидатор.
Помогите пожалуйста разобраться с urlManager...
Нужно из URL вот такого типа:
Сделать URL вот такого типа:
Уже пытался настраивать вот так:
Странно. Вроде правило
должно подходить.
Не получилось :(
а как сделать чтобы обрабатывались вот такие ссылки
/blog/kakoeto_imya-1 то есть контрллер blog actionView
вы не сталкивались с такой проблемой у Ulogin
file_get_contents(): php_network_getaddresses: getaddrinfo failed: Name or service not known
Не сталкивался.
У меня такая же ошибка:
Пока не решил.
Akulenok, у Вас какая версия php?
Здравствуйте!
Нет ли готового примера реализации мультиязычности на Yii2 ?
Не знаю. У меня нет.
Сегодня у кого-то появился: http://habrahabr.ru/post/226931/
Большое спасибо!
Никак не получается настроить URL
есть модуль pages
контроллер default
и экшн view
сейчас url строится
domain.ru/pages/default/view/id/1
хочу чтобы было
domain.ru/about (about - как алиас)
в правилах urlManager пишу
в моделе Pages
но не работает.
показывает 400 - некорректный запрос.
доступ к модулю по domain.ru/pages - тоже накрывается
однако когда с id, то работает, как
domain.ru/1/about
так тоже пробовал не помогало, а вот со своим классом все получилось
Спасибо, пропустил пример по созданию своего класса
и сразу же на заметку ошибки: где
пропущены скобки.
Исправил. Спасибо!
Интересная статейка, почерпнул много нового для себя. Мне вот интересно про метод PUT. С Yii ещё плохо знаком и из статьи не понял где вот это прописать и как использовать
Просто стоит задача сделать загрузку на Яндекс.Диск. Там как раз используется метод PUT для загрузки.
Можно подробнее про PUT? Зарание спасибо.
Здраствуйте, Дмитрий, Вот вроде роутинг в yii должен быть гибким, а вот у меня ну ни как не получается сделать так чтобы для ссылок statistic/order и statistic/order/564564 было одно правило. Два знаю как сделать, а вот одно бы?! Не подскажете как это сделать?
Забыл написать
statistic - контроллер
order - Экшн
564564 - параметр
Проще два.
Дмитрий, спасибо вы очень качественно пишете. Многое позаимствовал у вас в блоге. Столкнулся с такой проблемой : согласно правилам в htaccess, если файл например /zzz/xxx.css не найден, то запрос отправляется в index.php. Контрлллера zzz у нас нет и нужно чтобы выдавалась 404 ошибка, а у нас исполняется дефолтный контроллер. Как сделать чтобы если не найден контроллер выдавалась ошибка?
Можно просто не отправлять их на index.php:
Добрый день! Очень интересная статья! Можете подсказать, как получить доступ к модели, отправленной в шаблон отображения из контроллера в файле layout/column2.php ? Почему то у меня это получилось сделать только в product/item.php (контроллер/action)
Контроллер - ProductController
Action - actionItem
При рендеринге данные из контроллера передаются в product/item, потом сгенерированный HTML-код присваивается переменной $content, передаваемой в layout/column2. Чтобы что-то перебросить в шаблон можно сохранить это что-то в контроллере ($this). Из готовых вещей подойдут клипы: в представлении присваиваем $this->clips['model'] = $model и в шаблоне используем $this->clips['model'].
Но передача модели – это решение весьма странное. Логичнее всё-таки передавать готовый HTML-код. В шаблоне определить место:
а в представлении уже сгенерировать результат с использованием нашей модели и передать его наверх:
Спасибо огромное. Самый лучший сайт про yii и отзывчивый автор!
Подскажите пожалуйста такую историю! написал класс на основе CBaseUrlRule все хорошо работает но появилась нужда создавать ссылки на субдомен! но почему то ссылки создаются
как от этого избавиться?
Добавьте в свой класс:
Огромное спасибо за статью, и в частности за эту строчку:
нашёл решение своей проблемы, когда при установленном 'urlSuffix' => '.html' по умолчанию, вызов CreateAbsoluteUrl('img/image.jpg') генерирует site.com/img/image.jpg.html
Спасибо, Дмитрий, за эти две статьи.
Стало все понятно и ясно, благодаря вашим разъяснениям, чего не получалось с официального сайта и тд. Не хватало полной картины этого всего :).
Дмитрий, подскажите, пожалуйста, где копать:
Использую useStrictParsing. При переходе по не указанному URL ошибку обрабатывает accessControl и сперва просит авторизоваться. После авторизации пишет ошибку "у вас не достаточно прав..."
Должен, же, по идее перебрасывать на 404...
всё оказалось просто - не открыл в accessRules доступ к действию error в siteController
Спасибо!
Подскажите пожалуйста, где писать это:
Можно в действии контроллера или даже в его методе beforeAction.
Спасибо!
Этот вариант не будет работать с camelCase?
Например с CategoryProductController и StatusOrderController?
Ну поставьте преобразование:
Спасибо, попробую.
Есть такой вопрос по rules в yii2
у меня в rules правилах есть такие два правила
и такая загвоздка, в экшене index принимается только параметр service
хоть в ссылке я указываю название параметра
Например:
в обоих случаях в экшн приходит только service
Правила проверяются сверху вниз, поэтому diagnostic/string подпадает под первый шаблон.
Тобишь Yii не смотрит на название параметров, тогда правило
и параметр service в Url::to() я могу назвать его как угодно и он все равно его примет?
Смотрит. Если в Url::to укажете category, то cформирует по второму правилу. Но обратно спарсит по первому.
Извиняюсь не совсем понимаю, подскажите тогда если мне нужно несколько параметров что-бы различать их что есть что, как быть тогда? я это обошел с помощью суфикса типа
но мне кажется это не совсем хорошо.
Либо добавить префикс:
либо cделать составной путь:
либо сделать класс-правило DiagnosticUrlRule и в его методе parseUrl запросом в базу определять, категория это или сервис, и возвращать нужный маршрут.
Спасибо за ответ и за ваши уроки, все очень полезно, есть такое пожелание, те уроки которые в видео сделать и текстовыми, иногда удобно использовать в качестве шпаргалки если что то потзабыл
Если найдётся доброволец, который их в текст под диктовку перепишет, то будут. Не желаете этим заняться?
В принципе можно, на выходных.
Здравствуйте. У меня ошибка 404 Невозможно обработать запрос "blogController/blogj".
А вот кусок кода как я его вызываю.
И когда обрабатываю этот запрос, пишет вышеизложенную ошибку. В конфигах написаны url адреса
Здравствуйте.
Можно ли в роутинге в качестве шаблона использовать регулярное выражение, без указания контроллера. Мне нужно проверить на соответствие весь урл, за исключение домена.
И если не с ложно, примерчик ))
Можно.