Skip to content

Latest commit

 

History

History
621 lines (521 loc) · 16.7 KB

README.md

File metadata and controls

621 lines (521 loc) · 16.7 KB

Category ActiveRecord Behavior for Yii

Contains popular methods for work with plain and hierarchical categories in Yii

Readme

README RUS

Installation

Extract to protected/components.

Usage example

Attach any from this behaviors to your model. Use DCategoryBehavior for plain models and DCategoryTreeBehavior for hierarchical models.

[php]
class Tag extends CActiveRecord
{
    // ...
    
    public function behaviors()
    {
        return array(
            'CategoryBehavior'=>array(
                'class'=>'DCategoryBehavior',
                'titleAttribute'=>'title',
                'defaultCriteria'=>array(
                    'order'=>'t.title ASC'
                ),
            ),
        );
    }    
        
    private $_url;

    // Generates URL. Use simple `$model->url` instead of `Yii::app()->createUrl(...)`;
    public function getUrl()
    {
        if ($this->_url === null)
            $this->_url = Yii::app()->createUtl('blog/tag', array('tag'=>$this->title);
        return $this->_url;
    } 
    
    // ...
}

// Static pages
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;

    // Generates URL for every page. Use simple `$model->url` instead of `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;
    }  
    
    // ...
}

I recommend to create a base class Category and extend it in all subclasses

[php]
// Base class for all category models.
abstract class Category extends CActiveRecord
{        
    // ...
    
    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.position ASC, t.title ASC'
                ),
            ),
        );
    }     
}

/* 
 * Existing of redeclared a custom field `urlPrefix` in all subclasses allows simple 
 * generate URL in a base class without overriding of `getUrl()` method in childs
 */
class BlogCategory extends Category
{
    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'),
		));
	} 

    public function getUrl()
    { 
        ...
    }    
}

After attaching of this Behavior to your model you can use this public methods:

Specification

DCategoryBehavior

Common parameters:

Attribute Description Default
titleAttribute Model attribute, which used for a title showing. title
aliasAttribute Model attribute, which defined a alias. alias
urlAttribute Model property, which contains a url. Optionally your model can have a `url` attribute or a `getUrl()` method, which constructs a correct url for using our `getMenuList()` method. url
linkActiveAttribute Model property, which returns true for active menu item. Optionally declare own public `getLinkActive()` method in your model. linkActive
requestPathAttribute Set this request property if you can use default `getLinkActive()` method from this Behavior for `getMenuList()`. path
defaultCriteria Default criteria for all queries. array()

Common methods:

Method Description
findByAlias($alias) Finds model by alias attribute.
getArray() Returns primary keys of all items.
getAssocList() Returns a associated array ($id=>$title, $id=>$title, ...).
getAliasList() Returns a associated array ($alias=>$title, $alias=>$title, ...).
getUrlList() Returns a associated array ($url=>$title, $url=>$title, ...).
getMenuList() Returns items for zii.widgets.CMenu widget.
getLinkActive() Redeclare this method in your model for use `getMenuList()` or define in `requestPathAttribute` your $_GET attribute for url matching. It returns true if a current request url matches with category alias.

DCategoryTreeBehavior (extends DCategoryBehavior)

Content DCategoryBehavior specification and addons:

Additional parameters:

Attribute Description Default
parentAttribute Parent attribute. parent_id
parentRelation Parent relation. parent

Additional and overrided methods:

Method Description
findByPath($path) Finds a model by a path.
isChildOf($parent)* Checks for the current model is a child of the parent.
getChildsArray($parent=0)* Returns a array of primary keys of children items.
getAssocList($parent=0)* Returns a associated array ($id=>$fullTitle, $id=>$fullTitle, ...).
getAliasList($parent=0)* Returns a associated array ($alias=>$fullTitle, $alias=>$fullTitle, ...).
getTabList($parent=0)* Returns a tabulated array ($id=>$title, $id=>$title, ...).
getUrlList($parent=0)* Returns a associated array ($url=>$title, $url=>$title, ...).
getMenuList($sub=0, $parent=0)* Returns items for zii.widgets.CMenu widget.
getPath($separator='/') Constructs a full path for your current model.
getBreadcrumbs($lastLink=false) Constructs breadcrumbs for zii.widgets.CBreadcrumbs widget. Use `getBreadcrumbs(true)` if you want have a link in the last element.
getFullTitle($inverse=false, $separator=' - ') Constructs a full title for your current model.

* Argument $parent may contain a number, a model object or array of numbers. You may use:

  • Model::model()->getChildsArray();
  • Model::model()->getChildsArray(5);
  • Model::model()->getChildsArray(array(1, 3, 5));
  • Model::model()->getChildsArray($model) or $model->getChildsArray().

Using for the dropDownList() method:

[php]
<div class="row">
    <?php echo $form->labelEx($model, 'category_id'); ?><br />
    <?php echo $form->dropDownList(
        $model,
        'category_id',
        array_merge(
            array(''=>'[None]'), 
            BlogCategory::model()->published()->getTabList()
        )
    ); ?><br />
    <?php echo $form->error($model, 'category_id'); ?>
</div>

Using for CMenu widget (with caching):

[php]
<h2>All categories:</h2>
<?php $this->widget('zii.widgets.CMenu', array(
    'items'=>BlogCategory::model()->cache(3600)->getMenuList(10))
); ?>

<h2>Subcategories of <?php echo $category->title; ?>:</h2>
<?php $this->widget('zii.widgets.CMenu', array(
    'items'=>$category->cache(3600)->getMenuList())
); ?>

Usage sample in E-shop

Configuration file config/main.php:

[php]
return array(
    'components'=>array(
		'urlManager'=>array(
			'urlFormat'=>'path',
			'showScriptName'=>false,
			'rules'=>array(
                // ...
                
                'shop/<action:cart|order>'=>'shop/<action>',
                
                // http://site.com/shop/printers/home/laser/15
                'shop/<path:.+>/<id:\d+>'=>'shop/view',
                
                // http://site.com/shop/printers/home/laser
                'shop/<path:.+>'=>'shop/category',
                
                'shop'=>'shop/index',
                
                // ...
            ),
        ),
    ),
)

Base category model:

[php]
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(){
        // ...
    }  
    
    private $_url;

    // Generates URL. Use simple `$model->url` instead of `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;
    }   
    
    // ...
}

ShopCategory model:

[php]
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'),
		));
	}  
}

Product model:

[php]
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()->request->baseUrl . '/shop/' . $this->category->path . '/' . $this->id;
        return $this->_url;
    }
}

Controller:

[php]
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);

        // Mirrors protection) 
        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,
        ));
    }
}

View shop/index.php:

[php]
<?php
$this->pageTitle = 'Catalog';
$this->breadcrumbs array('Catalog');
?>

<h1>Catalog</h1>

<p>Categories:</p>
<?php $this->widget('zii.widgets.CMenu', array('items' => ShopCategory::model()->getMenuList()));?>

<?php echo $this->renderPartial('_loop', array('dataProvider'=>$dataProvider)); ?>

View shop/category.php:

[php]
<?php
$this->pageTitle = 'Catalog - ' . $category->getFullTitle();
$this->breadcrumbs = array_merge(
    array(
        'Catalog'=>$this->createUrl('shop/index'),
    ), 
    $category->getBreadcrumbs()
);
?>

<h1><?php echo CHtml::encode($category->title); ?></h1>

<p>Subcategories:</p>
<?php $this->widget('zii.widgets.CMenu', array('items' => $category->getMenuList()));?>

<?php echo $this->renderPartial('_loop', array('dataProvider'=>$dataProvider)); ?>

View shop/view.php:

[php]
<?php
$this->pageTitle = $product->title;
$this->breadcrumbs=array(
    'Catalog'=>$this->createUrl('shop/index'),
);

if ($product->category)
    $this->breadcrumbs = array_merge($this->breadcrumbs, $product->category->getBreadcrumbs(true));

$this->breadcrumbs[]= $product->title;
?>

<h1><?php echo CHtml::encode($product->title); ?></h1>

<?php if ($product->category): ?>
    <p>Category: <?php echo CHtml::link($product->category->title, $product->category->url); ?></p>
<?php endif; ?>

<p>Price: <?php echo $product->price; ?></p>