Категории

Всем привет. Подскажите как организовать вложенность категорий, т.е. к примеру /cat/subcat1/subcat2/test.html. Сейчас у меня две таблицы:

Новости:

id cat_id title


1 1 test

Категории:

id parent_id title


1 0 news

А как вот в ссылке вывести и выборку сделать правильно не соображу, перечитал данный топик http://www.yiiframework.com/forum/index.php/topic/44330-подскажите-как-реализовать-категории-и-подкат/ раз 20, все равно не пойму :(

у вас вложенность предполагается ограниченная строгая (категория/группа/элемент) или универсальная (категория1/категория2/категория3/ … категория_n/элемент)?

по всем признакам универсальная

в ней есть несколько проблем:

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

  2. определение группа является конечной или в ней есть вложенные группы

  3. привязка конечной группы к элементу

  4. каждый раз придётся проверять правильность переданного пути потому как вы написали "/cat/subcat1/subcat2/test.html" грубо говоря вам надо проверить цепочку

первое если группа cat если у неё группа subcat1 если у subcat1 группа subcat2 и тд

  1. сформировать ссылку опять же придётся подтянуть всю цепочку

может изложите изначальную задачу есть разные варианты реализации (более простые и эффективные) этот вариант просто ну очень грузовой! если вы рассчитываете на не большие объёмы то может и не нужна универсальная вложенность!

Мне бы в принципе хватило 5 уровней вложенности, т.к. более смысла я вообще не вижу делать, возможно три даже - т.е. /cat/subcat/subcat2/test.html Я думаю более не нужно в принципе. Самый простой вариант, который я рассматривал - это тупо формирование и запись url при добавлении новости к примеру. Т.е. добавляем новость, выбираем категорию - новости->культура. В итоге получаем готовый url - /новости/культура/новость.html Но это по сути хардкод, т.к. если у нас к примеру 100 новостей в категории новости->культура, что будет если мы захотим сменить название категории или еще чего. Поэтому вот решил поинтересоваться как сделать чтоб было все правильно :)

А у Вас не так много вариантов, кстати, с учетом того, что по-хорошему нужно проверять всё дерево на существование.

Т. е. если /cat1/cat2/cat3/.../catN/test.html должен открываться, а, скажем, /cat1/kat2/cat3/.../catN/test.html должен выдавать 404, то материализованный путь - это самый простой вариант. Проблема с переименованием решается триггерами, благо это не такая уж частая штука.

А вообще я бы посидел тут и послушал, какие еще варианты решения можно придумать. Возможно, народ решает при помощи каких-нибудь мутированных nested set?

Ок, спасибо :) Т.е. вы считаете что вариант с формированием статических url(не знаю как правильно их назвать) имеет право на существование ?

Он не только имеет право, он существует и активно используется (правда, чаще всего в связке с какими-нибудь приблудами типа постресного ltree). Взять хотя бы DMOZ.

Более того, в большинстве случаев дерево категорий довольно маленькое (скажем, до тысячи элементов), поэтому триггеры, отвечающие за целостность, особо не создают нагрузки.

Фишка в том, что если сеошники не упороты (и не меняют slug категорий по сто раз на дню), то денормализованный путь - это самый быстрый способ прогулок по деревьям. Поэтому если селектов существенно больше чем инсертов-апдейтов, то очень всё быстро и прикольно.

Но я бы все-таки послушал про другие методы реализации на практике. Задача-то частая, а вот дальше ltree я как-то не совал пока носа.

Ок, еще раз спасибо. Я вот тут набросал для "полной" новости вариант, но что-то как-то страшно получилось да еще и коряво:




public function actionView($category, $alias)

	{

		echo $category;

		echo $alias;

		

		$category = explode("/", $category);


		foreach ($category as $key => $value) {

			$cat[] = Category::model()->find('alias=:alias', array(':alias'=>$value));

		}

			

		foreach ($cat as $key => $c) {

			$ct[] =  $c['id'];

		}


		$cat_id = array_pop($ct);


		echo $cat_id;


		$node = Node::model()->find('alias=:alias AND category_id = :category_id', array(':alias'=>$alias, ':category_id' => $cat_id));


		var_dump($node);

}



Пойду дальше думать :(

мне больше нравится реализовывать на основе тегов как на http://rmcreative.ru/blog/tag/Yii+Yii2

и ссылка красивая и поиск проще

либо жесткая структура для каталогов товаров там обычно раздел/категория/группа но ссылки делаю строго раздел/алиас категория/алиас группа/алиас без всяких премудрств

работают стандартные правила легко настраивать

Спасибо, теперь примерно представляю как это будет. Но запнулся на одной вещи, а именно получении url - не могу выбрать правильно подкатегорию. Т.е. в таблице category к примеру есть




id | parent_id | name

---|-----------|------

 1 |     0     | Новости

 2 |     1     | Культура



Не пойму как выбрать по id потомка родителя, т.е. к примеру у нас Категория с id = 2, а как получить ссылку вида Новости/Культура, т.е. подняться по дереву до родителя ?

проше всего сделать в моделе функцию parent которая возвращает родительский объект если parent_id = 0 то возвращает null

функция parent




public function parent (){

if($this->parent_id == 0) return null;

return self::model()->findByPk($this->parent_id);

}



далее

в цикле




$urlCategories = array();

$model = AR::model()->findByPk($id);

while($model !== null){

$urlCategories[] = $model->name;

$model = $model->parent();

}

$urlCategories = array_reverse($urlCategories);

$url = '/some/'.implode('/', $urlCategories);



правда нужно экранизировать всё но я так для примера

ps. как я и говорил с формированием и разбором ссылок куча проблем!

Спасибо. Сорри, за тупой вопрос, а куда это ?




далее

в цикле

[code]

$urlCategories = array();

$model = AR::model()->findByPk($id);

while($model !== null){

$urlCategories[] = $model->name;

$model = $model->parent();

}

$urlCategories = array_reverse($urlCategories);

$url = '/some/'.implode('/', $urlCategories);



Написал в экшене, дык ругается, говорит нет метода parent(), через $this также не хочет :)

функцию parent должна быть в моделе категорий её туда над добавить я написал как она должна выглядеть

запись $model = AR::model()->findByPk($id); надо подправить на $model = НазваниеКлассаКатегорий::model()->findByPk($id);

по поводу куда это

это можно создать удобную функцию которая автоматом будет гинерить ссылку или ещё как то! я вам принцип работы говорю я же не знаю как у вас код организован и как вам будет удобнее это использовать

Да я вообще запутался, если честно. Ладно, спасибо, пойду дальше думать :)

Mihail, огромное Вам спасибо, вроде все получилось, правда пока что для полной "новости"

В модели Category написал, как вы и говорили:




public function parent()

{

    if($this->parent_id == 0) return null;

    return self::model()->findByPk($this->parent_id);

}	



В NodeController у меня так получается:




public function actionView($category, $alias)

	{

		$node = Node::model()->find('alias=:alias', array(':alias'=>$alias));


		$urlCategories = array();


		$model = Category::model()->findByPk($node->category_id);


		while($model !== null){

			$urlCategories[] = $model->alias;

			$model = $model->parent();

		}


		$urlCategories = array_reverse($urlCategories);


		$url = implode('/', $urlCategories);


		if($category != $url)

		{

			throw new CHttpException(404, 'Not found');

		}


		$node = Node::model()->find('alias=:alias', array(':alias'=>$alias));


		$this->render('view', array('model'=>$node));   

	}



Вроде работает, только вот не знаю, насколько это правильно, так делать :)

Подскажите еще один момент пожалуйста. Пытаюсь получить всех потомков, пишу




 function ShowTree($ParentID)

	{  

		$result = Category::model()->find('parent_id=:parent_id', array(':parent_id'=>$ParentID));

	        while ($result) {

	            echo $result['id'];

	            $this->ShowTree($result['id']); 

	            break;          

	    } 

	}



Получаю 123. А через запятую сделать, т.е. 1,2,3 не могу. Получаю только последнее значение, т.е. 3

$node = Node::model()->find(‘alias=:alias’, array(’:alias’=>$alias)); - второй вызов лишний и над добавить

if($node == null)

	{


		throw new CHttpException(404, 'Not found');


	}

поидее тут всё ок другие варианты при универсальном каталоге сложно представить

можно наверно придумать какие то более изящные решения но мне сложно так за раз их представить есть над чем подумать!




 function ShowTree($ParentID)

	{  

		$ids = array();

		$category = Category::model()->find('parent_id=:parent_id', array(':parent_id'=>$ParentID));

	        while ($category != null) {

	            $ids[] = $category->id;

	            $category = Category::model()->find('parent_id=:parent_id', array(':parent_id'=>$category->id));         

	    } 

	     echo implode(',', $ids);

	}



по идее так если следовать вашему коду но что то я в нём не вижу смысла потому что допустим есть

кат1 у неё в есть

==>кат2

==>кат3

у кат3 есть

====>кат4

====>кат5

допустим ты в функцию передаш id кат1 то выдаст либо только кат2 либо кат3,кат4

что именно тебе нужно чтоб выдавала функция?

Mihail, еще раз Огромное Вам спасибо, даже не представляете как вы мне помогли :)

Мне нужно было получить всех потомков родительской категории, т.е. к примеру по ссылке /novosti/ выбрать все записи которые принадлежат категории Новости и всем подкатегориям данной категории и т.д. Если взять ту таблицу, что я выше писал, получается я передаю parent_id, получаю всех его потомков+самого родителя(parend_id -> id).

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

CategoryController




public function actionView($category)

{

	$a = explode("/", $category);


	$c = array_pop($a);


	$category = Category::model()->find('alias=:alias', array(':alias'=>$c));


	$id = $this->ShowTree($category->parent_id);


	//var_dump($id); 


	$criteria = new CDbCriteria();

        $criteria->addInCondition('t.category_id', $id);

 

        $dataProvider = new CActiveDataProvider(Node::model()->cache(3600), array(

            'criteria'=>$criteria,

            'pagination'=> array(

                'pageSize'=> 10,

                'pageVar'=>'page',

            )

        ));

 

        $this->render('index', array(

            'dataProvider'=>$dataProvider,

            'category'=>$category,

        ));

	}


function ShowTree($ParentID)

{  

  $ids = array();

  $category = Category::model()->find('parent_id=:parent_id', array(':parent_id'=>$ParentID));

  while ($category != null) {

    $ids[] = $category->id;

    $category = Category::model()->find('parent_id=:parent_id', array(':parent_id'=>$category->id));         

  } 

    return $ids;

}



Все работает вроде, правда как вы сказали, есть над чем подумать :)

Блин, чет опять не то, рано радовался, пойду дальше думать :(

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

это делается так




function ShowTree($ParentID)

{  

  $items = array();

  $categories = Category::model()->findAll('parent_id=:parent_id', array(':parent_id'=>$ParentID));

  foreach($categories as $category){

    $items[] = array(

      'model' => $category,

      'items' => $this->ShowTree($category->id);

    );

  }

return $items;

}



если нужен весь список идишников то так




function ShowTree($ParentID, $ids = array())

{  

  $categories = Category::model()->findAll('parent_id=:parent_id', array(':parent_id'=>$ParentID));

  foreach($categories as $category){

    $ids[] = $category->id;

    $ids = $this->ShowTree($category->id, $ids);

  }

return $ids;

}



и возможно тут проблема

$id = $this->ShowTree($category->parent_id);

надо

$id = $this->ShowTree($category->id);

и возможно ешё так

$id[] = $category->id;