CakePHP 实例教程: Categories Acts as Tree

Tree是CakePHP 1.2的核心Behaviors之一,可以用来轻易的实现无限极分类,并呈现树状列表。

图片来源:Tree traversa,WIKIPEDIA

基础实例
建立数据表

CREATE TABLE `categories` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `slug` VARCHAR(255) NOT NULL,
  `parent_id` INT(11) DEFAULT NULL,
  `lft` INT(11) DEFAULT NULL,
  `rght` INT(11) DEFAULT NULL,
  `link_count` INT(11) NOT NULL DEFAULT '0',
  PRIMARY KEY  (`id`)
)  DEFAULT CHARSET=utf8;

 
字段说明:parent_id,lft,rght均为必要字段,parent_id是很常用的表层级关系的做法。但在这里,lft和rght才是重点。它们表示的是一个分类的左右边界。原理如下图所示

图片来源:Managing Hierarchical Data in MySQL,dev.mysql.com
建立Model
使用bake shell

$ cd app
$ cake bake
 
Category Model:

class Category extends AppModel {
    var $name = 'Category';
    var $actsAs = array('Tree');
}

 
Categories Controller:

class CategoriesController extends AppController {
    var $name = 'Categories';

 
Add Action

function add() {
        if (!empty($this->data)) {
            $this->Category->create();
            if ($this->Category->save($this->data)) {
                $this->Session->setFlash(__('The Category has been saved', true));
                $this->redirect(array('action'=>'index'));
            } else {
                $this->Session->setFlash(__('The Category could not be saved. Please, try again.', true));
            }
        }
        $this->set('categories', $this->Category->generatetreelist(null, null, null, '-- '));
}

 
add.ctp

<?php echo $form->create('Category');?>
   
         <?php __('Add Category');?>
    <?php echo $form->input('name');?>
    <?php echo $form->input('slug');?>
    <?php echo $form->select('parent_id', $categories);?>
   
<?php echo $form->end('Submit');?>

 
更进一步
取得层级分类

class Category extends AppModel {
    var $name = 'Category';
    var $actsAs = array('Tree');
    function getThreadCategories() {
        $this->recursive=-1;
        return $this->findAllThreaded(null,null,'lft asc');
    }

 
取得下属条目
当给分类下添加条目,比如Links时,基本的做法是

class Category extends AppModel {
    var $name = 'Category';
    var $hasMany = array(
            'Links' => array('className' => 'Link',
                                'foreignKey' => 'category_id',
            )
    );
}

 
这样就通过Link.category_id连接了Link和对应的Category。
包含下级分类所属条目
但很多时候我们需要一个分类下的条目时,是需要包含下级分类所属条目的,此时需要添加finderQuery,并去除foreignkey:

class Category extends AppModel {
    var $name = 'Category';
    var $actsAs = array('Tree');
    var $hasMany = array(
            'Links' => array('className' => 'Link',
                                'foreignKey' => '',
                                'finderQuery' => '
                                    SELECT
                                        `Links`.*
                                    FROM
                                        `links` AS `Links`
                                    JOIN
                                        categories Child ON Child.id=Links.category_id
                                    JOIN
                                        categories Category ON Category.id={$__cakeID__$}
                                    WHERE
                                        (Child.lft >= Category.lft)
                                    AND
                                        (Child.rght <= Category.rght)
                                    '
            )
    );
}

 
刷新条目计数

class Link extends AppModel {
    var $name = 'Link';
    var $belongsTo = array(
            'Category' => array('className' => 'Category',
                                'foreignKey' => 'category_id',
            )
    );
    function updateCounterCache ($keys = array(), $created = false) {
        parent::updateCounterCache($keys, $created);

        $category = $this->field('category_id');
        if (!$category) {
            return;
        }
        $conditions = array('Category.id' => $category);
        $fields = array('id', 'lft', 'rght');
        $recursive = -1;
        $limits = $this->Category->find('first', compact('conditions', 'fields', 'recursive'));
        $path = $this->Category->getPath($category, $fields);
        foreach ($path as $category) {
            extract ($category);
            $conditions = array(
                'Category.lft >=' . $Category['lft'],
                'Category.rght <=' . $Category['rght'],
                );
            $this->Category->updateAll(
                array('link_count' => intval($this->find('count', array('conditions' => $conditions)))),
                array($this->Category->escapeField() => $Category['id'])
            );
        }
    }
}

 
这样每当我们新建一个Link时,其所属分类及上级分类的link_count将会被自动刷新。有缺憾的是,如果我们是把一个Link从旧分类调整到新分类,旧分类的link_count不会更新。

Related posts:

  1. cakephp controller中设置viewVars时的陷阱 要从controller传递变量到view,需要使用set method。基本的用法是 $this->set('categories',$categories);   更顺手的写法是 $this->set(compact('categories'));   多个变量时可以这样写 $this->set('categories',$categories); $this->set('category',$category);...
  2. Alpha’s Categories Widget for wordpress 近来用Wordpress做一个小站,需要象Joomla一样能按Section展示多组分类。下了一个Breukie’s Categories Widget插件达成了这一功能。但当我随后升级到Wordpress 2.8的时候,这个插件却不能完好的支持。看了一下源码,原来Wordpress 2.8变更了Widgets的实现方法。于是只好自己动手,写了这个增强的Categories Widget插件:Alpha’s Categories Widget。 利用Alpha’s Categories...
  3. Wordpress主题berita几处修改记录 前阵子看到这篇《使用Wordpress定制企业网站》介绍的berita这个主题。界面我很喜欢,以前也确曾帮朋友用wordpress做过企业网站,于是就去下载了官方原版来用。 当然世事无完美,更没有什么主题能够完全适应各种需求。使用中发现了一些小问题,或者仅仅是不符合个人需求之处,做了一些改动,特此记录。 选择空分类作为博客页 设置面板里Blog Section Settings->Pick Category for Your Blog...