宇扬信息科技工作室

PHP无限级分类使用嵌套集合模型

发布: 2012-11-13 14:50:50 | 作者: 不详 | 来源: 本站整理 | 查看: 470

花了两个小时,把一个无限级分类的代码从我现在正在开发的项目中剥离了出来并完善了下,使其能够独立于原有项目而单独使用。核心文件有二:category.class.php及mysql.class.php,附带了一个演示文件index.php,请放到web目录中执行,当然,执行前还需先创建数据库及category表,表预览情况如下:

数据库及category表 

其中的效果测试页面截图:

效果测试 

本测试页面代码:

<?php

require 'category.class.php';

$category = new Category();

$cid1 = $category->add('大分类一');
$category->add('子分类一', $cid1);
$category->add('子分类二', $cid1);

$cid2 = $category->add('大分类二');
$category->add('子分类三', $cid2);
$category->add('子分类四', $cid2);

echo "<pre>";
echo "大分类一的子分类:". PHP_EOL;
print_r($category->getsChildren($cid1, 1));
echo PHP_EOL;
echo "大分类二的子分类:". PHP_EOL;
print_r($category->getsChildren($cid2, 1));
echo PHP_EOL;
echo "更多函数方法,请查看category.class.php文件源码";
echo "</pre>";

?>

category.class.php类文件内容:

<?php

class Category 
{
        
        /**
         * 表名
         *
         * @access private
         * @var string
         */
        private $_table;
        
        /**
         * 数据库对象
         *
         * @access private
         * @var object
         */
        private $_db;

        /**
         * 构造器
         * 
         * @access public
         */
        function __construct()
        {
                require_once 'mysql.class.php';
                $this->_table = 'category';
                try {
                        $this->_db = new MySQL('localhost', 3306, 'root', '', 'demo');
                        $this->_db->setEncoding('UTF8');
                } catch (Exception $e) {
                        exit($e->getMessage());
                }
        }
    
    /**
     * 增加节点
     *
     * @access public
     * @param string $cname
     * @param integer $pid
     * @return mixed
     */
    function add($cname, $pid = 1)
    {        
                //检查同级分类中是否有同名节点
                $has = $this->hasChildrenByName($pid, $cname);
            if (!$has && $parent = $this->get($pid)) {
            try {
                                $this->_db->beginTransaction();
                                //将所有后添加的结点的左值扩展2
                                $sql = "update {$this->_table} set lft=lft+2 where lft>". intval($parent['rgt']);
                                $this->_db->query($sql);
                                //把父分类的所有父级及后添加的结点右边界值扩展2
                                $sql = "update {$this->_table} set rgt=rgt+2 where rgt>=". intval($parent['rgt']);
                                $this->_db->query($sql);
                                //新加入节点,本节点的左值为父节点的原右值,右值为左值+1
                                $sql = "insert into {$this->_table}(pid, depth, lft, rgt, cname) values('{$pid}', '". (intval($parent['depth'])+1). "', '". intval($parent['rgt']). "', '". (intval($parent['rgt'])+1). "', '". $cname. "')";
                                $this->_db->query($sql);
                $cid = $this->_db->lastInsertId();
                                return $this->_db->commit() ? $cid : false;
            } catch (Exception $e) {
                    $this->_db->rollback();
                    return false;
            }
            }
            return $has;
    }
    
    /**
     * 取单个分类节点数据
     *
         * @access public
     * @param integer $id
     * @return array
     */
        function get($id)
        {
                $sql = "select * from {$this->_table} where cid={$id}";
            return $this->_db->fetch($sql);
        }
    
    /**
     * 删除节点
     *
     * @access public
     * @param int $id
     * @param array $node
     * @return bool
     */
    function del($id, $node = array())
    {
            $node || $node = $this->get($id);
            if ($node) {
                    try {
                            $this->_db->beginTransaction();
                                //删除当前要删除节点及其子节点
                                $sql = "delete from {$this->_table} where lft>=". intval($node['lft']). " and rgt<=". intval($node['rgt']);                                
                            $this->_db->query($sql);
                                //重置被删除节点父节点的左右值
                            $offset = $node['rgt'] - $node['lft'] + 1;
                                //重新校正所有左边界值大于被删除节点右边界值的节点(一般指创建在被删除节点之后的那些节点)
                                $sql = "update {$this->_table} set lft=lft-{$offset} where lft>". intval($node['rgt']);
                                $this->_db->query($sql);
                                //同上,校正被删除节点父级分类及后来创建分类的右边界值
                                $sql = "update {$this->_table} set rgt=rgt-{$offset} where rgt>". intval($node['rgt']);
                            $this->_db->query($sql);
                            return $this->_db->commit();
                    } catch (Exception $e) {
                        $this->_db->rollback();
                        return false;
                    }
            }
            return false;
    }

    
    /**
     * 根据某结点数据取得其子结点数
     *
     * @access public
     * @param int $id
     * @return integer
     */
    function getChildrenNum($id)
    {
            $num = 0;
                $node = $this->get($id);
            if ($node['depth'] > 0) {
                $num = max(($node['rgt'] - $node['lft'] - 1)/2, 0);
            }
            return $num;
    }
    
    /**
     * 获取最大节点深度
     *
     * @access public
     * @return int
     */
    function getMaxDepth()
    {
                $sql = "select depth from {$this->_table} order by depth desc limit 1";
        return (int)$this->_db->fetchOne($sql);
    }
    
    /**
     * 根据ID获取相应的分类项
     *
     * @access public
     * @param array $ids
     * @return array
     */
    function gets($id)
    {
                is_array($id) || $id = array($id);
                $sql = "select * from {$this->_table} where cid in (". implode(',', $id). ") order by cid asc";
            return $this->_db->fetchAll($sql);            
    }

    /**
     * 取得节点的子节点
     *
     * @param integer $id 节点ID
     * @param integer $depth 获取的子节点深度
     * @param boolean $backward 返回的数据是否包含本身
     * @return array
     */
    function getsChildren($id, $depth, $backward = false)
    {
            $node = $this->get($id);
                $sql = "select * from {$this->_table} where lft>";
                $sql.= ($backward ? '=' : ''). intval($node['lft']);
                $sql.= ' and rgt<'. ($backward ? '=' : ''). intval($node['rgt']);
                $sql.= ' and depth'. ($backward ? '<' : ''). '='. (intval($node['depth']) + $depth);
                $sql.= ' order by lft asc';
            return $this->_db->fetchAll($sql);
    }

    /**
     * 获取顶层结点
     *
     * @access public
     * @return array
     */
    function getsTop()
    {
                $sql = "select * from {$this->_table} where depth=1 order by lft asc";
            return $this->_db->fetchAll($sql);
    }
            
    /**
     * 取得节点的父节点
     *
     * @access public
     * @param integer $id
     * @return array
     */
    function getsParent($id)
    {
            $node = $this->get($id);
                $sql = "select * from {$this->_table} where lft<". intval($node['lft']). " and rgt>". intval($node['rgt']). " and depth>0 order by lft asc";
            return $this->_db->fetchAll($sql);
    }
    
    /**
     * 取得节点上一个父节点
     *
     * @access public
     * @param integer $id
     * @return array
     */
    function getParent($id)
    {
        $node = $this->get($id);
                $sql = "select * from {$this->_table} where lft<". intval($node['lft']). " and rgt>". intval($node['rgt']). " order by lft desc limit 1";
        return $this->_db->fetch($sql);
    }
    
    /**
     * 节点是否存在
     *
     * @param integer $id
     * @return int
     */
    function has($id)
    {
                $sql = "select count(*) from {$this->_table} where cid=". intval($id);
            return (bool)$this->_db->fetchOne($sql);
    }
        
    /**
     * 查询某结点是否存在子结点
     *
     * @access public
     * @param integer $id
     * @return integer
     */
    function hasChildren($id)
    {
                $sql = "select count(*) from {$this->_table} where pid=". intval($id);
            return (int)$this->_db->fetchOne($sql);
    }
            
    /**
     * 判断某节点下是否存在某名称的子节点
     *
     * @access public
     * @param integer  $cid
     * @param string $cname
     * @return integer
     */
    function hasChildrenByName($cid, $cname)
    {
                $sql = "select cid from {$this->_table} where cname='{$cname}' and pid={$cid}";
        return $this->_db->fetchOne($sql);
    }
        
        /**
     * 更新节点的值
     *
     * @access public
     * @param array $bind
     * @param array $where
     * @return bool
     */
    function update($id, array $bind)
    {
                $sql = "update {$this->_table} set ";
                foreach ($bind as $k=> $v) {
                        $sql.= "{$k}=". (is_string($v) ? "'{$v}'" : intval($v)). ", ";
                }
                $sql = rtrim($sql, ", ");
                $sql.= " where cid=". intval($id);
        return is_integer($this->_db->query($sql));
    }

}

?>


嵌套集合模型有别于平常创建无限级分类时使用的邻接表模型,能更快更好的检索整个分类表,我现在自己的项目中的分类表有四级8000多个分类,用这种方法完全能达到开发时的各种要求。

类文件中于关键几处写了些注释,其他地方较为浅显,故没加。mysql.class.php是一个简单封装的数据库执行类,如果你用不着,完全可以用你自己的类代替,只需稍改category.class.php中的一些地方即可。

    评论 0 次    最新评论(评论内容只代表网友观点,与本站立场无关!)
    发表评论
    点击更换验证码  

    ·本站大部分文章和内容来自互联网,如果您觉得我们侵犯了您的权益,请告诉我们!

    ·您在本站发表的作品,本站有权在网站内转载或引用!

    ·本站所提供的设计,摄影及其它作品,如需使用,请与原作者联系,版权归原作者所有。

    About 宇扬联系我们付款方式友情链接网站地图版权声明投稿须知帮助