DCategoryBehavior: Работа с категориями и списками в Yii
В процессе работы с фреймворком часто приходится запрашивать у моделей всевозможные массивы (для генерации меню, для выпадающих списков в формах поиска или редактирования сущностей), а также находить нужную категорию по составному пути.
Задача генерации списков
Рассмотрим довольно частую необходимость получения ассоциативного массива для выпадающего списка категории. В начале освоения Yii многие используют непосредственную генерацию ассоциативных массивов с нужными полями из выдачи моделей с помощью метода CHtml::listData()
прямо в коде представления:
class Category extends CActiveRecord { // ... }
<div class="row"> <?php $list = CHtml::listData(Category::model()->findAll(array('order'=>'title ASC')), 'id', 'title'); echo $form->labelEx($model,'category_id'); <br /> echo $form->dropDownList($model,'category_id', $list); <br /> echo $form->error($model,'category_id'); </div>
Копировать такой код не очень удобно, поэтому логично спрятать эту генерацию в саму модель:
class Category extends CActiveRecord { public function getAssocList() { $models = $this->findAll(array('order'=>'title ASC')); return CHtml::listData(models, 'id', 'title'); } }
<div class="row"> <?php echo $form->labelEx($model,'category_id'); <br /> echo $form->dropDownList($model,'category_id', Category::model()->getAssocList()); <br /> echo $form->error($model,'category_id'); </div>
Этот код уже лучше (не будем здесь обращать внимания на желательный для облегчения метода переход к DAO). Теперь достаточно все модели, которые где-либо фигурируют в выпадающих списках, оснастить методом getAssocList()
.
Некоторые советуют делать метод getAssocList()
статическим, чтобы не создавать лишний экземпляр вызовом Category::model()
:
class Category extends CActiveRecord { public static function getAssocList() { $models = self::model()->findAll(array('order'=>'title ASC')); return CHtml::listData(models, 'id', 'title'); } } <div class="row"> <?php echo $form->labelEx($model,'category_id'); <br /> echo $form->dropDownList($model,'category_id', Category::getAssocList()); <br /> echo $form->error($model,'category_id'); </div>
Но этот путь намного хуже, так как не поддерживает наследование и не даёт возможности использовать именованные группы условий вида Category::model()->published()->getAssocList()
.
Это один из часто используемых методов. Рассмотрим теперь другие.
Вложенные категории и ЧПУ
При введении ЧПУ на сайт, то есть при переходе с численных адресов
http://site.com/page/17 - страница http://site.com/shop/category/9 - категория 9 http://site.com/shop/category/15 - категория 15 http://site.com/shop/product/115 - товар
на человекопонятные адреса
http://site.com/page/payment http://site.com/shop/sergi/zoloto/krasnoe-zoloto http://site.com/shop/kolca/obruchalnie/zoloto/beloe-zoloto/s-brilliantom http://site.com/shop/kolca/obruchalnie/zoloto/beloe-zoloto/s-brilliantom/115
в моделях нужно хранить псевдонимы (добавить поле alias), а в контроллерах вместо поиска по идентификатору Category::model()->findByPk($id)
нужно придумать и использовать методы поиска по псевдониму findByAlias($alias)
или по пути findByPath($path)
. Метод findByPath()
должен разбивать путь kolca/obruchalnie/zoloto/beloe-zoloto/s-brilliantom
на элементы и находить нужную модель.
Варианты организации категорий
В определённый момент число моделей категорий начнёт расти. Появятся категории блога, категории магазина, категории портфолио и т.д. с методом getMenuList()
для генерации пунктов меню. Для вложенных категорий и вложенных статических страниц нам уже потребовалось ввести свои методы findByPath()
. Было бы неплохо сделать несколько удобных методов для построения различных списков. Разброс наборов одинаковых методов по разным моделям засоряет код, поэтому целесообразнее сделать универсальными и собрать все в одном месте. Рассмотрим два варианта.
1. Выделение общих методов в базовый класс
Для объединения общего кода можно выделить базовый абстрактный или конкретный класс Category, от которого наследовать все модели категорий.
abstract class Category extends CActiveRecord { public method findByAlias($alias) { return $this->findByAttributes(array('alias'=>$alias)); } } class ShopCategory extends Category { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{shop_category}}'; } }
В этом случае можно использовать метод ShopCategory::model()->findByAlias()
и остальные, реализованные в базовом классе. Но может возникнуть классическая проблема невозможности множественного наследования реализации, если вы вдруг захотите наследовать этот класс одновременно с каким-либо другим.
2. Выделение общих методов в поведение
Идея поведений очень хороша сама по себе, так как не сковывая разработчика никакими ограничениями поведения могут свободно «прилипать» к любой компоненту. Достаточно лишь выделить все необходимые нам методы в класс поведения и при необходимости легко подключать к любой модели. При этом можно также наследовать категории от базового класса:
class Page extends CActiveRecord { public function behaviors() { return array( 'SomeBehavior'=>array( 'class'=>'SomeBehavior', 'titleAttribute'=>'title', 'aliasAttribute'=>'alias' ), ); } } abstract class Category extends CActiveRecord { public function behaviors() { return array( 'SomeBehavior'=>array( 'class'=>'SomeBehavior', 'titleAttribute'=>'title', 'aliasAttribute'=>'alias' ), ); } public function rules(){ // ... } public function attributeLabels(){ // ... } // ... } class BlogCategory extends Category { public static function model($className=__CLASS__){ return parent::model($className); } public function tableName(){ return '{{blog_category}}'; } } class ShopCategory extends Category { public static function model($className=__CLASS__){ return parent::model($className); } public function tableName(){ return '{{shop_category}}'; } }
Поведение для работы с категориями
Предлагаю свою коллекцию часто используемых методов для работы со списками, одноуровневыми и иерархическими категориями. Методы собраны в два поведения: базовое (для простых одноуровневых моделей) и расширенное (для иерархических). Расширенное поведение наследуется от базового, поэтому сразу оба поведения к одной и той же модели подключать не надо.
Это поведение разбито на два класса: DCategoryBehavior для простых категорий и DCategoryTreeBehavior (наследуется от первого) для иерархических. Иерархические категории должны иметь внешний ключ и отношение relation, ссылающиеся на родительскую категорию (то есть быть устроены по принципу Adjacency List). Так как DCategoryTreeBehavior является наследником DCategoryBehavior, для иерархических моделей также доступны и все методы, работающие для простых списков.
Рассмотрим параметры подключения поведения и методы, которые можно использовать в модели:
Поведение DCategoryBehavior
Параметры:
Атрибут | Описание | По умолчанию |
---|---|---|
titleAttribute | Атрибут модели, содержащий название. | title |
aliasAttribute | Атрибут модели, содержащий псевдоним для составления URL. | alias |
urlAttribute | Атрибут, содержащий URL адрес категории или страницы. Вы можете либо хранить адрес в поле 'url' модели, либо определить в модели метод `getUrl()`, конструирующий адрес. Поведение будет запрашивать URL модели для генерации массива для меню методом `getMenuList()`. | url |
linkActiveAttribute | Свойство должно возвращать true если ссылка в меню должна быть активна. При желании Вы моджете переопределить в модели публичный метод `getLinkActive()` или задать $_GET-параметр `requestPathAttribute`, по которому сравнение путей будет производиться автоматически самим поведением. | linkActive |
requestPathAttribute | $_GET-параметр, по которому встроенный метод `getLinkActive()` определяет активность ссылки в меню при вызове `getMenuList()`. | path |
defaultCriteria | Параметры выборки, которые будут применяться во всех методах. Используйте, например, `array('order'=>'title')` для сортировки по алфавиту всех выборок. | array() |
Методы:
Метод | Описание |
---|---|
findByAlias($alias) | Замена `findByPk($id)` для поиска модели по псевдониму. |
getArray() | Возвращает массив идентификаторов всех категорий. |
getAssocList() | Возвращает ассоциативный массив вида ($id=>$title, $id=>$title, ...) для выпадающих списков. |
getAliasList() | Возвращает ассоциативный массив вида ($alias=>$title, $alias=>$title, ...). Можно использовать для списков в формах поиска. |
getUrlList() | Возвращает ассоциативный массив вида ($url=>$title, $url=>$title, ...). Полезен для автоподстановки ссылок в редакторе меню. |
getMenuList() | Возвращает массив для использования в виджете zii.widgets.CMenu. |
getLinkActive() | Используется методом `getMenuList()` для апределения активности ссылки в меню. Использует сравнение $_GET-параметра `requestPathAttribute` с текущим псевдонимом. Вы можете легко переопределить этот метод в своей модели. |
Поведение DCategoryTreeBehavior
Этот класс является наследником DCategoryBehavior, поэтому содержит все его методы и свои:
Параметры:
Атрибут | Описание | По умолчанию |
---|---|---|
parentAttribute | Атрибут модели, содержащий идентификатор дочерней категории. | parent_id |
parentRelation | Отношение, ссылающееся на родительскую категорию. | parent |
Методы:
Метод | Описание |
---|---|
findByPath($path) | Замена `findByPk($id)` для поиска модели по пути. |
isChildOf($parent)* | Проверка на принадлежность родительской модели. |
getChildsArray($parent=0)* | Возвращает массив идентификаторов дочерних элементов. |
getAssocList($parent=0)* | Возвращает массив с полными именами ($id=>$fullTitle, $id=>$fullTitle, ...). |
getAliasList($parent=0)* | Возвращает массив с полными именами и псевдонимами вместо идентификаторов ($alias=>$fullTitle, $alias=>$fullTitle, ...). |
getTabList($parent=0)* | Возвращает массив, оформленный с отступами у дочерних категорий ($id=>$title, $id=>$title, ...). |
getUrlList($parent=0))* | Возвращает ассоциативный массив вида ($url=>$title, $url=>$title, ...). Полезен для автоподстановки ссылок в редакторе меню. |
getMenuList($sub=0, $parent=0)* | Возвращает массив для виджета zii.widgets.CMenu. Можно указать число вложенных уровней. |
getPath($separator='/') | Собирает полный путь из псевдонимов. |
getBreadcrumbs($lastLink=false) | Возвращает массив для виджета for zii.widgets.CBreadcrumbs. Вызовите `getBreadcrumbs(true)` если необходимо добавить ссылку в последний элемент. |
getFullTitle($inverse=false, $separator=' - ') | Собирает полный заголовок. |
* В качестве аргумента $parent
можно использовать идентификатор, их массив или объект модели. Примеры:
- `Model::model()->getChildsArray()`;
- `Model::model()->getChildsArray(5)`;
- `Model::model()->getChildsArray(array(1, 3, 5))`;
- `Model::model()->getChildsArray($model)` равнозначно `$model->getChildsArray()`.
Для работы с этим поведением достаточно скопировать оба класса в любую директорию проекта (например, protected/components) и подключить нужное к любой модели.
class Tag extends CActiveRecord { // ... public function behaviors() { return array( 'CategoryBehavior'=>array( 'class'=>'DCategoryBehavior', 'titleAttribute'=>'title', 'defaultCriteria'=>array( 'order'=>'t.title ASC' ), ), ); } private $_url; // Генрирует URL. Используйте $model->url вместо Yii::app()->createUrl(...); public function getUrl() { if ($this->_url === null) $this->_url = Yii::app()->createUtl('blog/tag', array('tag'=>$this->title); return $this->_url; } // ... } // Модель статической страницы class Page extends CActiveRecord { // ... public function behaviors() { return array( 'CategoryBehavior'=>array( 'class'=>'DCategoryBehavior', 'titleAttribute'=>'title', 'aliasAttribute'=>'alias', 'urlAttribute'=>'url', 'requestPathAttribute'=>'alias', 'defaultCriteria'=>array( 'order'=>'t.title ASC' ), ), ); } private $_url; // Генерирует URL данной страницы. Используйте $model->url вместо Yii::app()->createUrl(...); public function getUrl() { if ($this->_url === null) $this->_url = Yii::app()->request->baseUrl . '/page/' . $this->cache(3600)->getPath() . Yii::app()->urlManager->urlSuffix; return $this->_url; } // ... } // Базовый класс для всех категорий на сайте abstract class Category extends CActiveRecord { // Переопределяется в дочерних классах protected $urlPrefix=''; // ... public function behaviors() { return array( 'CategoryTreeBehavior'=>array( 'class'=>'DCategoryTreeBehavior', 'titleAttribute'=>'title', 'aliasAttribute'=>'alias', 'urlAttribute'=>'url', 'requestPathAttribute'=>'path', 'parentAttribute'=>'parent_id', 'parentRelation'=>'parent', 'defaultCriteria'=>array( 'order'=>'t.title ASC' ), ), ); } public function rules(){ // ... } public function attributeLabels(){ // ... } public function scopes() { return array( 'published'=>array( 'condition'=>'t.public=1', ), ); } private $_url; // Генерирует URL просмотра данной категории. Используйте $model->url вместо Yii::app()->createUrl(...); public function getUrl() { if ($this->_url === null) $this->_url = Yii::app()->request->baseUrl . '/' . $this->urlPrefix . $this->cache(3600)->getPath() . Yii::app()->urlManager->urlSuffix; return $this->_url; } // ... } /* * Перезаписав значение поля urlPrefix в дочернем классе мы избавляемся от * необходимости переписывать метод getUrl() в каждой дочерней модели */ class BlogCategory extends Category { protected $urlPrefix='blog/'; public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{blog_category}}'; } public function relations() { return array_merge(parent::relations(), array( 'parent' => array(self::BELONGS_TO, 'BlogCategory', 'parent_id'), )); } }
Использование для выпадающего списка:
<div class="row"> <?php echo $form->labelEx($model, 'category_id'); <br /> echo $form->dropDownList( $model, 'category_id', array_merge( array(''=>'[Select category]'), BlogCategory::model()->published()->getTabList() ) ); <br /> echo $form->error($model, 'category_id'); </div>
Вывод меню:
<h2>Разделы блога:</h2> <?php $this->widget('zii.widgets.CMenu', array( 'items'=>BlogCategory::model()->cache(3600)->getMenuList(10)) ); <h2>Подразделы текущего раздела echo $category->title; :</h2> $this->widget('zii.widgets.CMenu', array( 'items'=>$category->cache(3600)->getMenuList()) );
Рассмотрим более сложный комплексный пример применения некоторых из методов в прототипе интернет-магазина.
Пример использования для каталога товаров
В этих примерах мы будем создавать URL-адреса категории и товаров простой конкатенацией строк. Этот вариант примитивен и не гибок. Кроме того,
ClinkPager
будет генерировать немного некорректные ссылки (будет кодировать слэши в категориях на спецсимволы). Для более корректной работы с адресами необходимо немного изменитьCUrlManager
и переписать методыgetUrl
на использование методаcreateUrl
согласно данной инструкции.
Прописываем маршруты в конфигурационном файле config/main.php:
return array( 'components'=>array( 'urlManager'=>array( 'urlFormat'=>'path', 'showScriptName'=>false, 'rules'=>array( // ... 'shop/<action:cart|order>'=>'shop/<action>', // site.com/shop/printers/home/laser/15 'shop/<path:.+>/<id:\d+>'=>'shop/view', // site.com/shop/printers/home/laser 'shop/<path:.+>'=>'shop/category', 'shop'=>'shop/index', // ... ), ), ), )
Базовый класс и модель категории каталога:
abstract class Category extends CActiveRecord { protected $urlPrefix = ''; // ... public function behaviors() { return array( 'CategoryTreeBehavior'=>array( 'class'=>'DCategoryTreeBehavior', 'titleAttribute'=>'title', 'aliasAttribute'=>'alias', 'urlAttribute'=>'url', 'requestPathAttribute'=>'path', 'parentAttribute'=>'parent_id', 'parentRelation'=>'parent', 'defaultCriteria'=>array( 'order'=>'t.title ASC' ), ), ); } public function rules() { return array( array('title, alias', 'required'), array('title, alias', 'length', 'max'=>255), array('parent_id', 'numerical', 'integerOnly'=>true), ); } public function attributeLabels(){ // ... } public function scopes() { return array( 'published'=>array( 'condition'=>'t.public=1', ), ); } private $_url; // Генерирует URL. Используйте `$model->url` вместо вызова `Yii::app()->createUrl(...)`; public function getUrl() { if ($this->_url === null) $this->_url = Yii::app()->request->baseUrl . '/' . $this->urlPrefix . $this->cache(3600)->getPath() . Yii::app()->urlManager->urlSuffix; return $this->_url; } // ... }
class ShopCategory extends Category { protected $urlPrefix = 'shop/'; public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{blog_category}}'; } public function relations() { return array_merge(parent::relations(), array( 'parent' => array(self::BELONGS_TO, 'ShopCategory', 'parent_id'), )); } }
Модель продукта:
class ShopProduct extends CActiveRecord { // ... public function relations() { return array( 'category' => array(self::BELONGS_TO, 'ShopCategory', 'category_id'), ); } private $_url; /* * Генерирует URL страницы просмотра продукта. * Используйте повсеместно запись $model->url вместо вызова Yii::app()->createUrl() */ public function getUrl(){ if ($this->_url === null) $this->_url = Yii::app()->request->baseUrl . '/shop/' . $this->category->path . '/' . $this->id; return $this->_url; } }
Контроллер каталога:
class ShopController extends Controller { public function actionIndex() { $criteria = new CDbCriteria; $criteria->order = 't.id DESC'; $dataProvider = new CActiveDataProvider( ShopProduct::model()->cache(300), array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>20, 'pageVar'=>'page', ) ) ); $this->render('index', array( 'dataProvider'=>$dataProvider, )); } public function actionCategory($path) { $category = ShopCategory::model()->findByPath($path); if (!$category) throw new CHttpException(404, 'Category not found'); $criteria = new CDbCriteria; $criteria->order = 't.id DESC'; $criteria->addInCondition('t.category_id', array_merge( array($category->id), $category->getChildsArray() )); $dataProvider = new CActiveDataProvider( ShopProduct::model()->cache(300), array( 'criteria'=>$criteria, 'pagination'=>array( 'pageSize'=>20, 'pageVar'=>'page', ) ) ); $this->render('category', array( 'dataProvider'=>$dataProvider, 'category' => $category, )); } public function actionView($id) { $product = ShopProduct::model()->with('category')->findByPk($id); // Защита от зеркал страниц if (Yii::app()->request->requestUri != $product->url) $this->redirect($product->url); if (!$product) throw new CHttpException(404, 'Not found'); $this->render('view', array( 'product'=>$product, )); } }
Представление shop/index.php:
<?php $this->pageTitle = 'Каталог'; $this->breadcrumbs array('Каталог'); <h1>Каталог</h1> <p>Категории:</p> $this->widget('zii.widgets.CMenu', array('items' => ShopCategory::model()->getMenuList())); echo $this->renderPartial('_loop', array('dataProvider'=>$dataProvider));
Представление shop/category.php:
<?php $this->pageTitle = 'Каталог - ' . $category->getFullTitle(); $this->breadcrumbs = array_merge( array( 'Каталог'=>$this->createUrl('shop/index'), ), $category->getBreadcrumbs() ); <h1> echo CHtml::encode($category->title); </h1> <p>Children categories:</p> $this->widget('zii.widgets.CMenu', array('items' => $category->getMenuList())); echo $this->renderPartial('_loop', array('dataProvider'=>$dataProvider));
Представление shop/view.php:
<?php $this->pageTitle = $product->title; $this->breadcrumbs=array( 'Каталог'=>$this->createUrl('shop/index'), ); if ($product->category) $this->breadcrumbs = array_merge( $this->breadcrumbs, $product->category->getBreadcrumbs(true) ); $this->breadcrumbs[]= $product->title; <h1> echo CHtml::encode($product->title); </h1> if ($product->category): <p>Category: echo CHtml::link($product->category->title, $product->category->url); </p> endif; <p>Price: echo number_format($product->price, 0, '.', ' '); </p>
большое спасибо
один вопрос
Эта чтобы выбрать всех товаров всех подкатегорий данной категории?
Да, для этого. Ещё не упомянул один полезный трюк:
В форме редактирования древовидных категорий в админке можно воспользоваться разностью массивов array_diff_key:
Это исключит из выпадающего списка всех детей текущей категории и её саму, что не даст их зациклить (не получится случайно сделать своим родителем самого себя или своего потомка).
спасибо
этого не знал
Спасибо! Наконец-то найден нормальный блог по yii
Спасибо! Я тоже ищу порой что-то серьёзное.
Разъясните пожалуйста один момент.
Почему если в DropDownList масив передать:
то значения option value задаются такие, как мы указали.
Но если передать, как в вашем примере:
то передаваемые значения option value уже не учитываются и отстёт начинается с нуля
Скриншот: http://clip2net.com/s/4KeMY4
Можно делать так:
а можно и так:
Спасибо! Так заработало как надо
getTabList() работает отлично. Понадобился обычный список getMenuList() и возникла проблема.
Ошибка: В классе Group и его поведениях не найден метод или замыкание с именем "getPath".
Посмотрел в файл DCategoryBehavior.php и в нём, в отличие от DCategoryTreeBehavior.php нет метода getPath. Хотя в вашем примере он вызывается
Как быть?
В обычном списке в методе getUrl() вашей модели нужно вместо $model->getPath() прямо использовать $model->alias.
Добрый вечер, Дмитрий
Подскажите в чем может быть ошибка, сделал вроде все по вашей инструкции (пример для исполльзования с каталогом товаров), но у меня не получается построить меню из метода "getMenuList", он возвращает пустой массив, так же как и метод "getUrlList".
Модель у меня содержит атрибуты: id, parent_id, title, slug
А так же отношение: parent
Метод getUrl так же добавлен в модель.
А другие методы работают?
Да, getAssocList, getAliasList, getTabList
getTabList у меня отлично работает в форме добавления/редактирования в drop down поле
Направите в нужном направлении, в чем может быть проблема?
А print_r($items) даёт вообще array()? Метод getUrl() в модели публичный?
Долго ковырялся в коде вашего поведения, но увы знаний не хватает для решения проблемы.
Опытным путем в обнимку с CVarDumper выяснилось, что проблема кроется в методе "_getMenuListRecursive".
Метод выдает просто "array()" если так вызывать "getMenuList()", если же вызвать "getMenuList(0,1)", то метод возвращает ветку с родителем "id=1".
Когда в методе "_getMenuListRecursive" вставляю "CVarDumper::dump($items,2,true);", получаю:
Как я понял, что ошибка в том, что первый элемент массива с пустым ключем, хотя он должен возвращать "0".
Сможете подсказать, в чем же все же дело?
Вот так выглядит моя таблица: http://s018.radikal.ru/i500/1304/6d/c4c4f0978d76.png
Хотя зря наверное грешу на "_getMenuListRecursive", так как в методе "getMenuList" CVarDumper::dump($categories,2,true); тоже возвращает:
Кажется мне, что все это из-за того что у меня атрибут parent_id возвращает null в этом куске кода:
в методах getUrlList и getMenuList
А если привести тип [(int)$item->{$this->parentAttribute}], то будет работать?
Обновил компонент. Добавил во все методы приведение типов для parent_id. Теперь не должно быть проблем с NULL.
Дмитрий, огромное вам Спасибо!
Все заработало! Ваши статьи и наработки по Yii это просто чудо!
спасибо за полезную инфу.
непонятно по adjacency list;
какого типа должен быть parentRelation? belongs_to, has_one?
и с какой моделью связь? на себя?
В приведённых здесь листингах это BELONGS_TO на саму себя:
Спасибо за интересную статью и за ваш блог!
К сожалению Ваш репозиторий category-behavior на github временно недоступен.
Да уж. Половина не открывается. GitHub сейчас глючит:
Помогите довести всё на 100% до конца, заменив:
на
Ведь если в urlManager`е изменить «shop/path:.+/id:\d+» на «shop/path:.+/alias:\w+», то это равносильно «shop/path:.+», и будет вызываться actionCategory и придёться сначало делать проверку ни продукт ли последний элемент $path
Подскажите как это правильнее сделать?
Проще не убирать «id:\d+» товара, а просто добавить «alias:[\w-]+» после него, то есть отображать товар в виде «shop/path/id/alias», то есть:
Тогда путанницы не будет.
Изменил правило на: 'shop/path:.+/id:\d+/alias:[\w-]+'=>'shop/view',
Ввожу в адресную строку: /shop/teh/phone/21/nokia-c2-03
Получаю тамже: /shop/teh/phone/21 И "error 404 Category not found"
Почему urlmanager обрезает alias?
А это правило стоит выше shop/path?
Да:
Вот ещё примеры:
Добавьте два правила для 'shop/view':
и в методе ShopProduct::getUrl() допишите прибавление $this->alias.
Иначе сейчас при заходе на
срабатывает ваше правило, но условие
перебрасывает редиректом на $product->url, который у Вас до сих пор равен
Здравствуйте, я вам наверно уже надоел, но у меня опять к вам вопрос :)
Сделал по аналогии ContentCategory и ShopCategory
ShopCategory работает отлично, но ContentCategory перестаёт работать в контроллере на строках:
Если ещё точнее то в DCategoryTreeBehavior.php на строке:
Таблицы для ContentCategory и ShopCategory идентичны
Проверил cached и $criteria и они идентичны, с разницей только имён классов.
Я уже голову поломал, не знаете в чём может быть проблема?
ContentCategory - пусто
ShopCategory - всё как надо
Видимо, ContentCategory наследуется не от Category.
В коде выше написано
Выводит и id родителя.
Смысл их сливать?
Исправил в коде поведения. Теперь не выводит. Спасибо.
Не за что :)
А почему именно AL? Чем NestedSet не устроил?
Год назад я особо не вдавался в особенности Nested Sets и нигде не использовал. При желании можно и для этого класс-наследник сочинить, например какой-нибудь
записав там публичные методы аналогично методам DCategoryTreeBehavior.
А вы можете выложить в конце статьи демо сайт с реализацией того, что вы описывали в этой статье? Я был бы вам безмерно благодарен, и, наверное, не только я.
Может быть рассмотрю когда-нибудь пример создания сайта на Yii или Yii2. Но это по обстоятельствам.
А что будет в _loop?
Вывод элементов
Здравствуйте!
Я ток начал изучать yii, пытаюсь колдовать с тестовым блогом, хочу прикрутить категории, но не выходит. Данная статья с поведением помогла, даже собрал свой компонент выводящий простой список категорий, но вот прикрутить имеющийся из репозитория в этой статье не выходит, что не делаю, выдаёт ошибку - "В классе BlogCategory и его поведениях не найден метод или замыкание с именем "published". Пробовал и так и так, копировал готовый код, пытался найти проблему, ноль эффекту, ошибка и всё, хотя в базе поле published есть, и так вроде всё норм...
Не могли бы вы выложить архив примера, не весь демо сайт, а чисто файлы по категориям приложения, и дамп базы, что бы можно было по коду глянуть что и где я не так делаю, а то непонятно, то ли в базе какие неточности, то ли я не туда код засунул...
Это именованная группа условий. Используется для удобства и записывается как
в модели и позволяет пользоваться собой как методом для подмешивания условий в запрос на выборку:
Использовать её необязательно.
ах да.., убрать из запроса "published()", я даже и не подумал. :)
Буду теперь знать для чего, пригодится.
Спасибо, помогло. Я эту функцию в модели Category вписал, как и в статье, и хоть модель BlogCategory и наследуется от Category, она не работает, по всей видимости потому что модели лежат в модулях(blog и category) а не в обшей категории моделей.
Кинул функцию прямо в BlogCategory и всё стало на свои места...
Спасибо за статью и Behavior.
Здравствуйте, спасибо огромное за расширение. Все работает отлично, но так как у меня мало опыта, не могу разобраться, как сделать так, что бы корневая категория не отображалась в path модели, то есть не фигурировала нигде вообще? Спасибо.
P.S. меню вывожу через getMenuList(), на данный момент ссылки категорий имеют вид http://yiitest/blog/CATEGORIES/Dizayn-v-promyshlennosti
хотелось бы избавиться от CATEGORIES
Пардон, разобрался, просто надо было внимательней читать мануал, в параметрах getmenulist просто задать parent=1
Все таки не понятно, может подскажете, как избавиться от корневой категории в пути url? Я так понимаю в методе getpath нужно что-то изменить, но не хватает опыта. подскажите пожалуйста. Тут в цикле как раз я так понял и происходит формирование пути:
все что мне приходит на ум, перебрать повторно $uri и вырезать root, но может есть другой, более красивый способ?
А можно убраь категорию CATEGORIES вообще из базы? Или она обязательно нужна?
Если я убираю из базы, возникает циклическая переадресация...
Но при этом надо parend_id у остальных обнулить.
Спасибо, все отлично заработало, а я уже в дебри полез.
Это всё аццке глубоко. Подскажите, от чего оттолкнуться? Знаю, что есть Nested Set, изучаю Yii, но не хотелось бы изобретать велосипед. Если можно, на русском. Спасибо!
Даже не знаю, что посоветовать. Nested Set, всё-таки, лучше для большого количества категорий.
Доброе время суток! Хотел узнать а нельзя ли во view использовать alias вместо id?
Можно. Только напишите URL-правило так, чтобы оно не пересекалось с правилами категорий.
Я в Yii новичок так что не судите строго. Правило написать в URLManager?
Да.
Здравствуйте Дмитрий. Не могу разобраться с getMenuList(). Прочитав все комменты, ни чего для себя не нашел.
А интересуют следующие моменты:
есть поля (исходя из исходников) в бд id, parent_id, title, url, alias ; для чего используется поле url, для описания полного пути?
Пример:
Родитель
id=1 | parent_id=0 | title='Родитель' | url='Мне не понятно что здесь(я поставил roditel)' | alias='roditel'
Вложенный элемент
id=2 | parent_id=1 | title='Вложенный элемент' | url='Мне не понятно что здесь(я поставил vlizhenniy-element)' | alias='vlizhenniy-element'
Если для Родителя url = 'roditel' , а для Вложенного элемента url = 'rodite/vlizhenniy-element' . То по-моему это не хорошо.
При использовании переноса раздела в другой раздел придется менять не только parent_id, но и url.
Возможно я ошибаюсь, что так должно все выглядеть, вот если как многие другие писали посмотреть дамп бд и работу скриптов другое дело.
Вот у меня выбирается $category = Categories::model()->findByPath($path=roditel);
далее я загоняю в Cmenu вот так
выводит
а по идее должен
Если меняю _getMenuListRecursive() - то все ломается в других местах. Пробовал 'url'=>$item->{$this->urlAttribute} 247 строка , поменять на 'url'=>$item->getPath()
Поле url в базе хранить не надо. Должен быть метод-геттер getUrl() вроде приведённого:
Именно он срабатывает при вызове $model->url.
Все заработало когда я внес корректировки в Ваш код, возможно вы как то по-другому использовали это.
в общем вот
и уже использую это поведение
Как-то странно работает. Вроде таблица создана по правилам, а метод getFullTitle возращае одному итему такое:
Аренда автобусов от компании - Аренда автобусов - Транспорт - Транспорт - Транспорт...
Тоже самое касается и вывода пути. Если итем привязан к самой верхней категории, то в урле будет так
/category/category/item_url вместо /category/item_url где category одно и тоже (два раза выводит поле alias из таблицы)
А чему равны id и parent_id у категории «Транспорт»?
Тут спасибо за ответ, я немного переделал код, уже не помню что допиливал, но уже работает все как надо. Спасибо!
Есть еще один момент.
Код
В ключе active всегда будет адрес *URL раздела, а виджет CMenu воспринимает active как true или false поэтому все элементы меню всегда будут активны в данном случае.
Там linkActiveAttribute, а не linkAttribute.
всмысле? виджет CMenu читает массив, видит, что у итема в active есть что-то и считает это true, делает каждый элемент активным.
В linkActiveAttribute содержится имя свойства:
То есть вызов
равносилен
Но так как это свойство, а не поле, то магическим методом вызывается геттер getLinkActive(), который возвращает true или false.
Вы путаете getUrl() и getLinkActive().
Сделал все, как в приведенных примерах, за исключением имен таблиц.
При переходе по ссылке shop/index получаю ошибку:
В строке 16 ShopController.php
ShopProduct::model()->cache(300),
А в ShopProduct есть метод model()?
$this->cache(3600)->getPath()
по факту ничего не кэширует. использую CFileCache и он включён.
и почему-то дебаггер 2 раза заходит в метод getPath в бихевере.
пока не отловил почему.
Под yii2 будет?
Возможно.
Нам очень надо!!! Помочь?
А что должно хранится в базе в полях url и alias?
В alias - типа category1 или category2 а в url - category1/category2 так?
Да, в alias хранится именно category1 или category2.
Поля url в таблице быть не должно. Его роль в модели должен выполнять геттер getUrl().
А есть ли аналогичное расширение на yii2? Было бы очень круто!
Дмитрий, есть вопрос.
Вы говорите, что поле URL в БД не должно быть, что формирование должно быть у геттера getUrl().
У меня оно не работает, а вот если я в таблицу добавляют поле URL и там прописываю ( catalog/catalog2 ) то ссылка отлично срабатывает, т.е я прописываю родителя.
Как исправить это. Возможно геттер у меня не срабатывает.
И еще как вывести полные хлебные крошки? Спасибо
Нашел такой выход, вроде работает :)
А как получить список\массив всех родительских элементов?
Отнаследоваться и дописать метод getParentsList().