Генерация URL для вложенных категорий в Yii

Окна и папки

Стандартный класс CUrlManager в Yii (да и в других фреймворках) позволяет собирать URL динамически на основе правил маршрутизации. В интернет-магазинах и блогах часто используются многоуровневые категории. Попробуем использовать их в Yii.

Правила маршрутизации для категорий

Предположим, что в нашем магазине (блоге, портфолио или в любом другом разделе сайта) нужно обеспечить работу, например, с такими адресами:

http://site.com/shop/computers - категория "Компьютеры"
http://site.com/shop/computers/23 - товар с ID = 23

Для разбора и создания таких адресов нам достаточно добавить в конфигурацию urlManager такие правила:

'shop/<action:cart|order>'=>'shop/<action>',
'shop/<category:[\w_-]+>/<id:[\d]+>'=>'shop/show',
'shop/<category:[\w_-]+>'=>'shop/category',
'shop'=>'shop/index',

В первой строке мы предусмотрительно описали действия контроллера, которые не нужно путать с категориями (чтобы по адресу shop/cart мы попадали в корзину, а не в товары категории cart). От этого можно избавиться также предварив адрес категории ключевым словом category. Тогда наши адреса вместо shop/... примут вид shop/category/...:

'shop/category/<category:[\w_\/-]+>/<id:[\d]+>'=>'shop/show',
'shop/category/<category:[\w_\/-]+>'=>'shop/category',
'shop'=>'shop/index',

В любом случае, эти правила позволят работать с нашим контроллером:

class ShopController extends Controller
{
    public function actionIndex()
    {
        // Вывод списка всех товаров
    }
 
    public function actionCategory($category)
    {
        // Вывод списка товаров категории
    }
 
    public function actionShow($category, $id)
    {
        // Отображение страницы товара    
    }
}

Но что делать, если у нас должна быть поддержка вложенных категорий? Разберём такой пример:

http://site.com/shop/computers/printers/laser - вложенная категория "Лазерные принтеры" 
http://site.com/shop/computers/printers/laser/37 - товар с ID = 37

Предыдущие правила для такого случая не подойдут. Категория должна передаваться в контроллер целиком (в виде computers/printers/laser), то есть именованный параметр <category> должен включать в себя и обратный слэш «/». Добавим этот символ в наши шаблоны:

'shop/<action:cart|order>'=>'shop/<action>',
'shop/<category:[\w_\/-]+>/<id:[\d]+>'=>'shop/show',
'shop/<category:[\w_\/-]+>'=>'shop/category',
'shop'=>'shop/index',

В крайнем случае можно разрешить все символы, используя точку:

'shop/<action:cart|order>'=>'shop/<action>',
'shop/<category:.+>/<id:[\d]+>'=>'shop/show',
'shop/<category:.+>'=>'shop/category',
'shop'=>'shop/index',

Заметьте, что жадный шаблон <category:[\w_\/-]+> «проглотит» весь адрес до конца строки, поэтому дополнительные параметры должны либо располагаться в начале адреса в виде shop/тип/...категория...

'shop/<type:\w+>/<category:[\w_\/-]+>/<id:[\d]+>'=>'shop/show',

либо должны быть предварены любым каким-нибудь легко распознаваемым префиксом вроде shop/...категория.../type/... или shop/...категория.../type_..., соответственно правила будут такими

'shop/category/<category:[\w_\/-]+>/type/<type:\w+>'=>'shop/category',

Теперь при переходе по адресу http://site.com/shop/computers/printers/laser мы будем попадать на действие ShopController::actionCategory, параметр $category которого будет содержать путь computers/printers/laser. Остаётся лишь найти нужную модель категории по этому пути и вывести список товаров:

class ShopController extends Controller
{    
    const PRODUCTS_PER_PAGE = 20;
 
    public function actionCategory($category)
    {
        // Ищем категорию по переданному пути
        $category = ShopCategory::model()->findByPath($category);
        if ($category === null)
            throw new CHttpException(404, 'Not found');
 
        $criteria = new CDbCriteria();
        $criteria->addInCondition('t.category_id', array_merge(array($category->id), $category->getChildsArray()));
 
        $dataProvider = new CActiveDataProvider(ShopProduct::model()->cache(3600), array(
            'criteria'=>$criteria,
            'pagination'=> array(
                'pageSize'=>self::PRODUCTS_PER_PAGE,
                'pageVar'=>'page',
            )
        ));
 
        $this->render('category', array(
            'dataProvider'=>$dataProvider,
            'category'=>$category,
        ));
    }    
 
    public function actionShow($category, $id)
    {
        $model = $this->loadModel($id)
 
        $this->render('show', array('model'=>$model));            
    }
}

Здесь мы воспользовались методом findByPath и getChildsArray модели ShopCategory. Условие

$criteria->addInCondition('t.category_id', array_merge(array($category->id), $category->getChildsArray()));

позволяет выбрать товары из текущей категории и всех её дочерних.

Эти методы можно создать в модели самому, а можно подключить для этих целей поведение DCategoryTreeBehavior.

Созданние URL для вложенных категорий

Воспользуемся стандартным методом CUrlManager::createUrl или CUrlManager::createAbsoluteUrl для сборки адреса для категории computers:

echo $this->createAbsoluteUrl('shop/index'); 
echo $this->createAbsoluteUrl('shop/category', array('category'=>'computers')); 
echo $this->createAbsoluteUrl('shop/show', array('category'=>'computers', 'id'=>12));

Мы получим адреса в полном соответствии с заданными нами маршрутами:

http://site.com/shop
http://site.com/shop/computers
http://site.com/shop/computers/12

Теперь попробуем сделать это же, но с вложенными категориями

echo $this->createAbsoluteUrl('shop/category', array('category'=>'computers/printers/laser')); 
echo $this->createAbsoluteUrl('shop/show', array('category'=>'computers/printers/laser', 'id'=>12));

Мы получим не совсем то, что хотели:

http://site.com/shop/computers%2Fprinters%2Flaser
http://site.com/shop/computers%2Fprinters%2Flaser/12

Это происходит из-за того, что все параметры метод CUrlRule::createUrl кодирует функцией urlencode. Соответственно, в нашей категории перекодируются и все слэши.

Есть несколько способов исправить это неудобство:

  • Создать класс ShopUrlRule и использовать его вместо маршрутов;
  • Переопределить CUrlManager и убрать из его метода createUrl экранирование слэшей;
  • Не использовать createUrl для создания адресов, а конкатенировать их вручную.

Первый способ требует некоторых усилий, но он не универсальный, так как нужно будет создавать клоны этого класса для каждого раздела сайта. Третий способ не подойдёт, так как createUrl требуется для генерации ссылок на страницы виджетом CListPager.

Рассмотрим второй способ. Создадим класс-наследник UrlManager, который будет заменять код «%2F» обратно на слэш:

class UrlManager extends CUrlManager
{
    public function createUrl($route, $params=array(), $ampersand='&')
    {
        return $this->fixPathSlashes(parent::createUrl($route, $params, $ampersand));
    }
 
    protected  function fixPathSlashes($url)
    {
        return preg_replace('|\%2F|i', '/', $url);
    }
}

Это весь код. Нужно указать наш класс UrlManager в параметре class конфигурации компонента Yii::app()->urlManager:

return array(
    'components'=>array(
        'urlManager'=>array(
            'class'=>'UrlManager',
            'urlFormat'=>'path',
            'showScriptName'=>false,
            'rules'=>array(
 
                'shop/<category:[\w_\/-]+>/<id:[\d]+>'=>'shop/show',
                'shop/<category:[\w_\/-]+>'=>'shop/category',
                'shop'=>'shop/index',
                // ...
 
            ),
        ),
    ),
);

Теперь подобные адреса у нас будут строиться правильно на всём сайте.

Упрощение создания адресов

Как известно, в представлениях мы можем использовать $this->createUrl, а в виджетах и моделях Yii::app()->createUrl или Yii::app()->controller->createUrl для создания адресов ссылок.

Если у нас есть модель

class ShopProduct extends CActiveRecord
{    
    public function relations()
    {
        return array(
            'category' => array(self::BELONGS_TO, 'ShopCategory', 'category_id'),
        );
    }
}

и если у категории есть метод getPath(), который генерирует полную строку вида parent/parent/category, то в представлении мы можем «легко» генерировать адреса ссылок:

<?php
<h2><a href="<?php echo $this->createUrl('shop/show', array('category'=>$product->category->getPath(), 'id'=>$product->id)); ?>"><?php echo CHtml::encode($product->title); ?></h2>
<p>Категория: <a href="<?php echo $this->createUrl('shop/category', array('category'=>$product->category->getPath())); ?>"><?php echo CHtml::encode($product->category->title); ?></h2>

Действительно «легко»? Чтобы не запоминать каждый раз маршруты и не путаться в них проще добавить геттер getUrl в наши модели. Для работы ShopCategory::getPath возьмём то же поведение DCategoryTreeBehavior:

class ShopCategory extends CActiveRecord
{    
    public function behaviors()
    {
        return array(
            'CategoryBehavior'=>array(
                'class'=>'DCategoryTreeBehavior',
                'titleAttribute'=>'title',
                'aliasAttribute'=>'alias',
                'parentAttribute'=>'parent_id',
                'parentRelation'=>'parent',
                'requestPathAttribute'=>'category',
                'defaultCriteria'=>array(
                    'order'=>'t.sort ASC, t.title ASC'
                ),
            ),
        );
    }
 
    private $_url;
 
    public function getUrl()
    {
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('shop/category', array('category'=>$this->cache(3600)->getPath()));
        return $this->_url;
    }
}
class ShopProduct extends CActiveRecord
{    
    public function relations()
    {
        return array(
            'category' => array(self::BELONGS_TO, 'ShopCategory', 'category_id'),
        );
    }
 
    private $_url;
 
    public function getUrl()
    {
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('shop/show', array('category'=>$this->category->cache(3600)->getPath(), 'id'=>$this->id));
        return $this->_url;
    }
}

Теперь можно использовать метод $model->getUrl() или эквивалентное свойство $model->url. Код представления предельно упрощается:

<?php
<h2><a href="<?php echo $product->url; ?>"><?php echo CHtml::encode($product->title); ?></h2>
<p>Категория: <a href="<?php echo $product->category->url; ?>"><?php echo CHtml::encode($product->category->title); ?></h2>

Это теперь можно использовать для защиты от дублирования адресов товаров:

class ShopController extends Controller
{        
    public function actionShow($category, $id)
    {
        $model = $this->loadModel($id)
 
        if (Yii::app()->request->getUrl() != $model->url)
            $this->redirect($model->url, true, 301);        
 
        $this->render('show', array('model'=>$model));            
    }
}

Теперь если будет неправильно указана категория

http://site.com/shop/computers/printers/laser/37
http://site.com/shop/computers/laser/37
http://site.com/shop/anypath/37

то произойдёт перенаправление на правильный URL.

После этого можно спокойно переносить товар между категориями и не беспокоиться о редиректах.

Комментарии

 

Александр

А как дела с пагинацией и сортировками?
Указывать их через "?"

Ответить

 

Дмитрий Елисеев

Если важны ЧПУ, то можно, например, указать все сочетания в маршруте:

'shop/&lt;category:[\w_\/-]+&gt;/sort/&lt;sort:[\w\.]+>/page/&lt;page:\d+&gt;'=>'shop/category',
'shop/&lt;category:[\w_\/-]+&gt;/sort/&lt;sort:[\w\.]+&gt;'=>'shop/category',
'shop/&lt;category:[\w_\/-]+&gt;/page/&lt;page:\d+&gt;'=>'shop/category',
'shop/&lt;category:[\w_\/-]+&gt;'=>'shop/category',
Ответить

 

larein

Есть несколько способов исправить это неудобство ...

есть 4ый способ, у подключив к CUrlManager поведение (behaviors) в конфиге, добавив например метод createPathUrl()
но вызывать придется так: Yii::app()->urlManager->createPathUrl();

Ответить

 

Дмитрий Елисеев

Да, это будет работать, но только при генерации ссылок вручную. Метод createUrl используем не только мы, но и сам Yii. Если мы оставим оригинальный createUrl, то CLinkPager и CSorter (используемые в ClistView и CGridView) будут по-прежнему вызывать его для генерации сортировки и паджинации, и всё сломается.

Ответить

 

larein

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

Ответить

 

Генадий

Спасибо за статью, очень помогло. Можно вас попросить, не могли бы написать про УРЛ на поддоменах, к примеру пользователей вынести на поддомены, или разделы, или города?

Ответить

 

Дмитрий Елисеев

Спасибо. На похожий вопрос я отвечал уже на форуме здесь. Если не сможете разобраться, то могу подробнее с примерами кода изложить.

Ответить

 

Стас

Отличная статья, равно как и все. Радует что yii описывайте именно Вы. Лаконично, полно, вообщем не просто "лишь бы написать".
Спасибо.

Ответить

 

Дмитрий Елисеев

Спасибо за отзыв.

Ответить

 

Евгений

Сделал все как описано, но если категория родительская, то она даже не попадает в метод actionCategory и показывается Error 404, думаю это из-за urlManager, но там все как описано у вас, включая переопределенный класс UrlManager

Ответить

 

Дмитрий Елисеев

А какие маршруты для них написаны? Используются ли дефисы или подчёркивания?

Ответить

 

Евгений

Все как у вас, только вместо ShopController у меня CategoryController.

'rules' => array(
    'category/<category:[\w_\/-]+>/<id:[\d]+>' => 'site/view',
    'category/<category:[\w_\/-]+>' => 'category/category',
    'category' => 'category/index',
),

если ссылка /science то получаю 404.
если /science/cosmos то все нормально. где-то на маршрутах вызов спотыкается.

Ответить

 

Дмитрий Елисеев

Вроде второе правило должно срабатывать на адреса

/category/science/cosmos
/category/science 
Ответить

 

Евгений

А как вы думаете в чем может быть ошибка?

Ответить

 

Дмитрий Елисеев

Даже не знаю. Попробуйте переставлять местами эти и другие правила, разрбраться, какой именно контроллер генерирует ошибку 404.

Ответить

 

Евгений

А насколько важна в методе findByPath($path) строчка
$this->parentAttribute . '=0'
Потому что без неё родительские категории работают. В базе данных у меня parent_id определенно как NULL а не 0. Как лучше решить эту проблему?

Ответить

 

Дмитрий Елисеев

Чтобы при переходе по адресу /shoes он по слову shoes не нашёл случайно какую-нибудь одноимённую вложенную вроде /man/winter/shoes.

Исправил в поведении этот фрагмент на

't.' . $this->parentAttribute . ' iS NULL OR t.' . $this->parentAttribute . '=0'

Теперь должно работать.

Ответить

 

Евгений

Спасибо. Теперь работает

Ответить

 

standalone

А как быть с nestedset категориями?

Ответить

 

Дмитрий Елисеев

Также организовать в моделях рекурсивные меоды getPath(), findByPath() и подобные.

Ответить

 

larein

есть у меня маленькое поведение реализующее findByPath(), getPath() и некоторые другие методы, которое подключается одновременно с NestedSetBehavior. закинул сюда http://codepad.org/XBhyQfxb

Ответить

 

larein

з.ы. некоторые моменты не доработаны для универсальности, но всегда же можно допилить)

Ответить

 

Евгений – icemen.ru

Абалдеть. Все так классно придумано и устроено, что смог все у себя сделать используя ваши поведения, но со своим контентом и другими адресами, но все почему-то) заработало. Теперь надо разобраться где я так все удачно угадал).

Вы убили сразу два зайца, даже три. И так все коротко и ясно. Теперь мы знаем что такое поведения, как сделать вложенные категории и работать с ЧПУ. Вы гений!

Ответить

 

Pavlov Alexey

Подскажите пожалуйста, а можно как нибудь избавиться от названия контроллера? Что бы ур выглядел вот так: http://site.com/computers/23

Спасибо

Ответить

 

Дмитрий Елисеев
'<category:[\w-]+>/<id:\d+>' => 'catalog/view',
Ответить

 

Роман

Подскажите, пожалуйста, почему не отрабатывает правило:

'<module:\w+>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>'

если только явно не прописать правило:

'admin/news/create' => 'admin_news/create',

где admin_news:

'admin_news' => '\application\modules\admin\controllers\NewsController'

Хотя бы подскажите направление, куда копать.

Ответить

 

Дмитрий Елисеев

Сначала определитесь, где admin/news или admin_news у Вас. Что справа, а что слева. Посчитайте число слешэй в правилах.

Ответить

 

Роман

У меня модуль - admin, контроллер - news и действие - create. Не отрабатывает стандартное правило, в чем может быть проблема, не пойму..

Ответить

 

Дмитрий Елисеев

Не срабатывает по адресу /admin/news/create?

Ответить

 

Роман

Да

Ответить

 

Роман

Не поможете?

Ответить

 

Дмитрий Елисеев

Напишите, какие вообще есть правила до стондартных и какая ошибка отображается.

Ответить

 

Роман
'urlManager' => array(
            'urlFormat' => 'path',
            'showScriptName' => false,
            'rules' => array(
                // Front
                '/' => 'front/index',
                // Admin
                //'admin' => 'admin/index',
                'admin/user' => 'admin_user/index',
                'admin/config' => 'admin_config/index',
                //'admin/news' => 'admin_news/index',
                'admin/news/admin' => 'admin_news/admin',
                'admin/news/view' => 'admin_news/view',
                'admin/news/create' => 'admin_news/create',
                'admin/news/update' => 'admin_news/update',
                'admin/news/delete' => 'admin_news/delete',

                '<controller:\w+>/<id:\d+>' => '<controller>/view',
                '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',
                '<controller:\w+>/<action:\w+>' => '<controller>/<action>',
                '<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>',
            ),
        ),

Ошибка по url: /admin/news/create - "Системе не удалось найти запрашиваемое действие "news"."

Ответить

 

Роман

если закомментировать

'admin/news/create' => 'admin_news/create'

то ошибка, не срабатывает правило последнее правило (module/controller/action)

Ответить

 

Дмитрий Елисеев
'rules' => array(
    '/' => 'front/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>',
    '<module:\w+>/<controller:\w+>' => '<module>/<controller>/index',
    '<module:\w+>' => '<module>/default/index',
),
Ответить

 

Роман

Так не работает, в этом весь и вопрос..

Ответить

 

Дмитрий Елисеев

Если у Вас только один модуль admin, то сделайте так:

'rules' => array(
    '/' => 'front/default/index',

    '<module:admin>/<controller:\w+>/<id:\d+>' => '<module>/<controller>/view',
    '<module:admin>/<controller:\w+>/<action:\w+>/<id:\d+>' => '<module>/<controller>/<action>',
    '<module:admin>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>',
    '<module:admin>/<controller:\w+>' => '<module>/<controller>/index',
    '<module:admin>' => '<module>/default/index',

    '<controller:\w+>/<id:\d+>' => '<controller>/view',
    '<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',
    '<controller:\w+>/<action:\w+>' => '<controller>/<action>',
),
Ответить

 

Роман

front - у меня тоже отдельным модулем (2 модуля)

Ответить

 

Дмитрий Елисеев

Тогда для чего нужны правила для простых контроллеров? Вот эти:

'<controller:\w+>/<id:\d+>' => '<controller>/view',
'<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',
'<controller:\w+>/<action:\w+>' => '<controller>/<action>',
Ответить

 

Роман

Если удалить все правила кроме

'<module:\w+>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>'

выдает ошибку: Невозможно обработать запрос /admin/news/create

Ответить

 

Евгений

$criteria->addInCondition('t.category_id', array($category->id) + $category->getChildsArray());

У меня + затер первый массив с category->id осталось только то что из getChildsArray()!!! пришлось воспользоваться array_merge()

Ответить

 

Дмитрий Елисеев

Спасибо! Исправил.

Ответить

 

Denis Anonimous

Здравствуйте.
Буду очень благодарен если объясните такой момент. Никак не получается разобраться.

Желаемая структура url:

catalog/ - список всех категорий (catalog это модуль)
catalog/alias_category/ - список товаров данной категории
catalog/alias_category/alias_manufacturer/ - список товаров данной категории принадлежащий указанному производителю
catalog/alias_category/alias_manufacturer/alias_product/ - страница товара

Такое вообще возможно?
Какой тогда должен быть контроллер с какими экшенами и какие правила писать в urlManager?
Спасибо

Ответить

 

Denis Anonimous

Вопрос решил. Сделал так.

DefaultController
public function actionIndex()
public function actionCategory($aliasc) 
public function actionManufacturer($aliasc,$aliasm) 
public function actionProduct($aliasc,$aliasm,$aliasp)
'<module:catalog>' => '<module>/default/index',
'<module:catalog>/<aliasc:[\w \-]+>' => '<module>/default/category',
'<module:catalog>/<aliasc:[\w \-]+>/<aliasm:[\w \-]+>' => '<module>/default/manufacturer',
'<module:catalog>/<aliasc:[\w \-]+>/<aliasm:[\w \-]+>/<aliasp:[\w \-]+>' => '<module>/default/product',

Получил свой желаемый вид

http://test.ru/catalog
http://test.ru/catalog/mobile
http://test.ru/catalog/mobile/apple
http://test.ru/catalog/mobile/apple/iphone_5s
Ответить

 

Дмитрий

Статья хорошая, но мне кажется что тут есть недоработка и давольно таки серьезная. Допустим есть у нас 3 категории вложенные, в поле бд в последней будет url "category1/category2/category3", и тут мы решаем что нужно переименовать "category2" на "test" ее alias автоматически тоже меняется и теперь нам нужно найти ее дочерние категории и все дочерние дочерних и заменить у них url - в донной ситуации мы должны получить "category1/test/category3". А у вас это совсем не упоменается. Как быть в этой ситуации?

Ответить

 

Дмитрий Елисеев

У меня нет поля url в БД.

Ответить

 

Дмитрий

А где вы берете "alias" для каждой из категорий?

Ответить

 

Дмитрий Елисеев

Поле alias как раз есть.

Ответить

 

Дмитрий

Так в "alias" хранится толь псевдоним. А откуда вы берете данные для генерации url - например второго уровня и выше? Или вы используете "Nested Sets" для этого?

Ответить

 

Дмитрий Елисеев

Посмотрите на методы getUrl() в статье.

Ответить

 

Дмитрий

Насколько я понимаю, в методе "ShopProduct->getUrl()" вы обращаетесь к методу "ShopCategory->getPath()" который в свою очередь находится в поведении "DCategoryTreeBehavior", а вот в поведении уже вы в цикле получаете родителей и добавляете их в массив, который в свою очередь преобразовываете в строку - URI.
Правильно я вас понимаю?

Если правильно - тогда вопрос а "Nested Sets" или элементарная генерация URI в момент добавления записи и хранения ее в базе, не эффективнее ли будет?

Ответить

 

Дмитрий Елисеев

Nested Sets эффективнее в любом случае.

Ответить

 

alex – zawebis.com

спасибо. а для yii2 можно пример добавить?

Ответить

 

alex – zawebis.com

хотя как вариант просто переписать под себя уже готовый виджет Menu и все.

а вот как правильно составить правила для многоуровнего меню?

Ответить

 

Алексей

Делаю категории в action index

    public function actionIndex($id = false)
    {
    if ($id){
        $query = Articles::find()->where(['status' => articles::ARTICLE_STATUS_ACTIVE, 'category_id'=>$id]);
    } else {
        $query = Articles::find()->where(['status' => articles::ARTICLE_STATUS_ACTIVE]);
    }
   
    $dataProvider = new ActiveDataProvider([
        'query' => $query,
        'sort'=> ['defaultOrder' => ['created_at'=>SORT_DESC]]
    ]);

    return $this->render('index', [
        'dataProvider' => $dataProvider,
    ]);
}

подскажите, пожалуйста, как мне в меню указать ссылку на категорию? ($id категории куда дописать?)

['label' => 'Экономика', 'url' => ['/articles/default/index']],
Ответить

 

Дмитрий Елисеев
['label' => 'Экономика', 'url' => ['/articles/default/index', 'id' => $id]],
Ответить

 

Вадим

У меня есть пара вопросов.
в конфигурации приложения:

'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
    ],
]

везде написано вставьте, а куда конкретно не могу нигде нарыть.
1 какой файл является конфигом приложения? config/web.php
2 это сюда или еще куда?

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'components' => [

'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        ['class' => 'yii\rest\UrlRule', 'controller' => 'statistics'],
    ],
],
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'фыва',
        ],
...
Ответить

 

Nikolay

Дмитрий, а как реализовать такие же красивые ссылки для фильтров(типа: цена, цвет, размер,высота) на странице категории, если все параметры динамически создаются в админке?

В конечном результате хочеться увидить что-то типа

http://site.ru/computers/laptop/apple/spacegrey/15/2015

Где computers/laptop - категория и подкатегория
Apple - бренд
Spacegray - цвет
2015 - год выпуска

Ответить

 

Дмитрий Елисеев

Через своё правило UrlRule, которое будет в нужной последовательности склеивать и парсить.

Ответить

 

Сергей

Дмитрий, вопрос немного в оффтоп. Не подскажете как реализовать вывод подкатегорий и категорий в Breadcrumbs (категории/подкатегории хранятся в одной таблице) на странице записи в Yii 2?

Ответить

 

Дмитрий Елисеев
$category = $model->category;
do {
    array_unshift($this->params['breadcrumbs'], [
        'label' => $category->title,
        'url' => ['category', 'id' => $category->id],
    ]);
} while ($category = $category->parent);
$this->params['breadcrumbs'][] = $model->title;
Ответить

 

Сергей

Огромное спасибо, Дмитрий!

Ответить

 

Oleg Ua

Спасибо за помощь :)

Ответить

 

Сергей

Дмитрий, не подскажите пожалуйста как реализовать генерацию url через отдельный класс с помощью интерфейса UrlRuleInterface когда записи хранятся внутри вложенной категории(третий уровень вложенности) в Yii2? То есть через методы parseRequest($manager, $request) и createUrl($manager, $route, $params).

В обычном правиле rules это выглядит так:

'<categorySlug:[a-z0-9-_]+>/<subCategorySlug:[a-z0-9-_]+>/<slug:[a-z0-9-_]+>' => 'service/view',

И еще ошибка при таком правиле, что не выводятся картинки с библиотеки yii2-images, хотя путь в атрибуте src у них прописывается. Может регулярные выражения неверно прописаны? Если убрать одну вложенность, то картинки нормально отображаются.

Буду очень признателен за помощь.

Ответить

 

Дмитрий Елисеев

Разбиваете путь на части:

$chunks = explode('/', $request->pathInfo);

и последовательно ищете по этим частям.

Ответить

 

Григорий Степенко

Дмитрий, спасибо за отличную статью. Использовал описанный в статье метод вместе с DAO и наткнулся на препятствие при решении вопроса фильтрации товаров в рубриках и дочерних рубриках по свойствам товаров. Для хранения свойств товаров использую две таблицы в одной названия свойства, во второй значения свойств, привязка к товарам и привязка к названию свойства. Не испытываю затруднений в формировании DAO запросов, а вот при передаче значений в действие контроллера возникли затруднения.
Подскажите, каким образом можно фильтровать товары по значениям свойств товаров внутри рубрик при такой организации работы UrlManager? Похожий вопрос задавал Nikolay 26.02.2016 но у меня немного другая ситуация. И если можно раскройте немного ответ который был дан Николаю.

Ответить

 

Дмитрий Елисеев

Ответ Николаю - сделать класс, который будет парсить адрес и компоновать его обратно.

Ответить

 

боби фио

А как исключить экшн из правила, чтобы получилось что-то типа

'/' => 'site/index',
'site/<action:[^(index)]>' => 'site/<action>',

чтобы в урл не дублировалась страница по / и 'site/index

.htaccess не в счет

Ответить

 

Петр

Здравствуйте, Дмитрий. Подскажите, пожалуйста, как сделать ЧПУ для фильтров на сайте. Неужели нужно все варианты в rules перебрать? И что делать, если не все параметры обязательны? Например, допустим есть четыре фильтра : сортировать по языку (lang), по минимальной сумме (min), по максимальной сумме (max) и по определенной валюте (valuta).

Ответить

 

Дмитрий Елисеев

Всё нестандартное можно сделать через класс-правило.

Ответить

 

Петр

Благодарю за помощь и быстрый ответ )))) попробуем разобраться

Ответить

 

Андрей – nova.dsk-stolica.ru

Добрый день.

Использую Yii2 - хочу сделать SEO ссылки вида:
мой-сайт/название-категории/название-продукта.html
мой-сайт/nike/nike-air-90.html

Мои UrlManager правила:

'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [               

                [
                'pattern'=>'<url:.+>',
                'route' => 'product/view',
                'suffix' => '.html',
                ], 
               
                [
                'pattern'=>'<url_category:.+>',
                'route' => 'category/view',
                'suffix' => '',
                ],                
                                            
                'search' => 'category/search',
            ],
        ],

Мой контроллер КАТЕГОРИЙ:

class CategoryController extends AppController{
public function actionView($url_category){		

		$category = Category::find()->where(['url_category' => $url_category])->one();
	    $id = $category->id; //id
	    $url_category = $category->url_category; //url

		if(empty($category))
			throw new \yii\web\HttpException(404, 'Такой категории нет.');
		
		$query = Product::find()->where(['category_id' => $id]);
		$pages = new Pagination(['totalCount' => $query->count(), 'pageSize' => 500, 'forcePageParam' => false, 'pageSizeParam' => false]);
		$products = $query->offset($pages->offset)->limit($pages->limit)->all();

		//Формируем Title страницы
		$this->setMeta($category->name, $category->keywords, $category->description);
		return $this->render('view', compact('products', 'pages', 'category'), ['compcatname' => $compcatname]);
	}}	

Мой контроллер ТОВАРА:

class ProductController extends AppController{

	public function actionView($url_category, $url){	

		$product = Product::find()->where(['url' => $url])->one();
	        $id = $product->id; //id
	        $url = $product->url; //url
		
		//Вывод ошибок 404
		if(empty($product))
			throw new \yii\web\HttpException(404, 'Такого товара в каталоге не существует.');		

		$hits = Product::find()->where(['hit' => '1'])->limit(12)->all();		

		$this->setMeta($product->name, $product->keywords, $product->description);
		return $this->render('view', compact('product', 'hits', 'productname'));
	}

1) Вопрос

Категории у меня работают правильно - то есть открываются по ссылкам вида:
мой-сайт/название-категории
мой-сайт/nike

А вот товары не получается никак реализовать, чтобы сначала шла КАТЕГОРИЯ/ТОВАР вида:
мой-сайт/название-категории/название-продукта.html
мой-сайт/nike/nike-air-90.html

2) Вопрос

Как мне правильно формировать ссылки во view?

Сейчас у меня ссылки вида:

Ссылка на категорию:
<?= Url::to(['/nike'])?> А хочется получать ссылку автоматом.

Ссылка на товар:

мой-сайт/nike-air-90.html

А хочется получать ссылку на товар вместе с категорией вида:
мой-сайт/nike/nike-air-90.html

Ответить

 

Андрей – nova.dsk-stolica.ru

Разобрался сам вот решение :) Но есть 1 косяк. сначала покажу код:

[
                'pattern'=>'<url_category:[a-z0-9-_]+>/<url:[a-z0-9-_]+>',
                'route' => 'product/view',
                'suffix' => '.html',
                ], 
               
                [
                'pattern'=>'<url_category:[a-z0-9-_]+>',
                'route' => 'category/view',
                'suffix' => '',
                ],

Теперь ссылки приняли настоящий SEO + ЧПУ вид:

мой-сайт/nike
мой-сайт/nike/nike-air-90.html

А теперь проблема:

Когда я захожу на товар по такой ссылке:

<?= Url::to(['product/view', 'url' => $product->url, 'url_category' => $category->url_category]) ?>

вид такой ссылки получается: http://мой-сайт/nike/nike-air-90.html

Но, если убрать в ссылке Категории любую букву или ее изменить например так:

мой-сайт/ne/nike-air-90.html или мой-сайт/nеkkie-re-ewe/nike-air-90.html

а таких категорий в Базе Данных нет!

То почему то ссылка все равно отрабатывает и показывается! Как я понимаю Категория не проверяется из БД - что очень плохо. Как в этом случае быть подскажите пожалуйста. Ведь если неправильно написать ссылку то должен срабатывать ошибка 404 код в контроллере Категория:

 
if(empty($category))
    throw new \yii\web\HttpException(404, 'Такой категории нет.');

а проверка не отрабатывает - почему?

Ответить

 

Дмитрий Елисеев

Такие нестандартные вещи решаются созданием своих классов-правил.

Ответить

 

Павел

Как можно в yii2 исправить проблему с кодированием слеша '/' в %2F при добавлении LinkPager::widget на вложенную страницу каталога.
Интернет магазин имеет вложенные категории /catalog/muzhskoe в которой много товаров. нужна пагинация. в итоге при формировании url виджетом пагинатора получается catalog%2Fmuzhskoe ?

Ответить

 

Дмитрий Елисеев

Можно, напирмер, через свой класс-правило.

Ответить

 

Павел

Спасибо, разрешил данным способом

Ответить

 

Алексей

Здравствуйте!

При отправке формы у меня создается примерно такой урл:

tagkls.ru/color?form[greenRound]=22&form[greenSquare]=12

могли бы подсказать как изменить имена get-параметров и убрать название формы
(например - form[greenRound] - переименовать в -- greenR), чтобы получилось вот так:

tagkls.ru/color?greenR=22&greenS=12

Спасибо.

Ответить

 

Дмитрий Елисеев

В Yii2 в модели формы переопределите метод formName:

public function formName() {
    return '';
}
Ответить

 

Алексей

Оказывается всё так просто.

Большое спасибо!

Ответить

 

Дмитрий

Здравствуйте, как сделать так, чтобы тире между двумя параметрами не учитывалось?
Есть правило вида:

'rasstoyanije-<fromCity:[\w\ ]+>-<toCity:[\w\-\ ]+>' => 'distance/betweencities',

После его разбора если города идут через тире или пробел, то ничего не работает.
Если добавить в правило - ( [\w\- ] ) - то города могут некорректно читаться, учитывается следующее тире после параметра.

Ust-kaminsk-ufa - получится fromcity = Ust-kaminsk-ufa, toCity = ''

Ответить

 

Дмитрий Елисеев

Создайте класс-правило и парсите в нём.

Ответить

 

Артур

Подскажите, пожалуйста...
А как получить товары категории с учетом вложенных...
Например по адресу site.com/shop/computers/printers/laser
нужно получить товары, у которых категория = "laser"...
А вот на странице site.com/shop/computers
нужно получить товары из категорий: "computers", "printers", "lasers" и т.д., то есть из текущей и всего дерева вглубь...

Ответить

 

Дмитрий Елисеев

Достать все id вложенных категорий и искать товары по всем ['category_id' => $ids].

Ответить

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

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>