改进型先根遍历算法

来源:互联网 发布:康佳网络电视怎么插u盘 编辑:程序博客网 时间:2024/06/07 17:53
<?php/** * 无效的分类Id * */class NodeNotFoundException extends Exception {public $id;function __construct($id){$this->id = $id;parent::__construct("指定 [ID = {$id}] 的节点不存在");}}/** * 节点名称已经存在 */class NodeNameExistsException extends Exception{public $name,$parentId;function __construct($name ,$parentId) {$this->name = $name;$this->parentId = $parentId;        parent::__construct("指定 [NAME = {$name} ,PARENT_ID = {$parentId}] 的分类已经存在");            }    }/** * 节点分类 模型 用“改进型先根遍历算法”在数据库中存储层次化的数据(通常所说的无限分类) * * 由于“改进型先根遍历算法”要求所有分类都是唯一一个根分类的子分类。 * 所以 NodeModel 假定一个名为“_#_ROOT_NODE_#_”的分类为唯一的根分类。 * * 应用程序在调用 NodeModel::create() 创建第一个分类时,会自动 * 判断根分类是否存在,并创建根分类。 * * 对于应用程序来说,“_#_ROOT_NODE_#_”分类是不存在的。所以,应用程序 * 可以创建多个父分类 ID 为 0 的“顶级分类”。这些顶级分类实际上就是 * “_#_ROOT_NODE_#_”分类的直接子分类。 * * 添加 deep 这个属性用于设置分类的深度,用于按名称查找分类,比如: * '/图书/IT/技术/PHP' 其 deep 就 为  * * 注意: 分类名称 中不能带有 "/" 符号,不然会出现意向不到的问题 * <sql> CREATE TABLE `nodes` ( `node_id` int(11) NOT NULL auto_increment, `parent_id` int(11) NOT NULL, `name` varchar(64) collate utf8_unicode_ci NOT NULL, `left_value` int(11) NOT NULL, `right_value` int(11) NOT NULL, `deep` int(4) NOT NULL, `created_at` datetime default NULL, `updated_at` datetime default NULL, PRIMARY KEY  (`node_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; * </sql> *  */class NodeModel {/** * @var CoreDb */protected $_dbo = NULL;    /**     * 数据表名称,在继承类中可能会被覆盖     *     * @var string     */    public $tableName = 'nodes';    /**     * 主键字段名,在继承类中可能会被覆盖     *     * @var string     */    public $primaryKey = 'node_id';    /**     * 根节点名     *     * @var string     */    protected $_rootNodeName = '_#_ROOT_NODE_#_';        /**     * 直系子节点名称是否唯一     *     * @var boolean     */    protected $_uniqueNameSupport = true;function __construct(CoreDb $dbo){$this->_dbo = $dbo;$this->init();}protected function init(){$this->tableName = $this->_dbo->getDbUtils()->getTableName($this->tableName);}function find($cond, $fields='*'){$sqlCond = CoreDbSqlHelper::parseConditions($this->_dbo,$cond);if ($sqlCond) $sqlCond = "WHERE {$sqlCond}";$qfields = CoreDbSqlHelper::qfields($fields,$this->tableName);$sql = "SELECT {$qfields} FROM {$this->tableName} {$sqlCond}" . CoreDbSqlHelper::getLimitSql(1);return $this->_dbo->getRow($sql);}function findByPkv($nodeId, $fields='*') {    return $this->find(array($this->primaryKey=>$nodeId),$fields);    }    /** * 添加用户 并返回插入的主键编号 *  * @var array $user *  * @return int */protected function insert(array $node){if (!isset($node['name'])) return false;$user['created_at'] = CURRENT_TIMESTAMP;$sql = CoreDbSqlHelper::getInsertSQL($node,$this->tableName);if ($sql){if ($this->_dbo->execute($sql,$node,true))return $this->_dbo->lastInsertId();}return false;}    /**     * 添加一个节点,返回该节点的 ID     *     * @param array $node     * @param int $parentId     *     * @return int     */    function create(array $node, $parentId=0) {        $parentId = (int)$parentId;        if ($parentId) {                    $parent = $this->find(array($this->primaryKey=>$parentId));            if (!$parent) {                // 指定的父节点不存在                throw new NodeNotFoundException($parentId);            }        } else {            // 如果 $parentId 为 0 或 null,则创建一个顶级节点            $parent = $this->find(array('name' => $this->_rootNodeName));            if (!$parent) {                // 如果根节点不存在,则自动创建                $parent = array(                    'name' => $this->_rootNodeName,                    'left_value' => 1,                    'right_value' => 2,                    'parent_id' => -1,                    'deep' => 0,                );                if (!$this->insert($parent)) {                    return false;                }            }            // 确保所有 _#_ROOT_NODE_#_ 的直接字节点的 parent_id 都为 0            $parent[$this->primaryKey] = 0;        }        //检验直系子分类名称是否唯一if ($this->_uniqueNameSupport && $this->nameExists($node['name'],$parent[$this->primaryKey])){throw new NodeNameExistsException($node['name'],$parent[$this->primaryKey]);}        // 加上事务功能        $this->_dbo->startTrans();                // 根据父节点的左值和右值更新数据        $sql = "UPDATE {$this->tableName} SET left_value = left_value + 2 " .               "WHERE left_value >= {$parent['right_value']}";        $this->_dbo->execute($sql);        $sql = "UPDATE {$this->tableName} SET right_value = right_value + 2 " .               "WHERE right_value >= {$parent['right_value']}";        $this->_dbo->execute($sql);        // 插入新节点记录        $node['parent_id'] = $parent[$this->primaryKey];        $node['deep'] = $parent['deep'] + 1; //路径深度定义        $node['left_value'] = $parent['right_value'];        $node['right_value'] = $parent['right_value'] + 1;                $id = $this->insert($node);                $this->_dbo->completeTrans();        return $id;    }/** * 检验直系子分类名称是否唯一 * * @param string $name * @param int $parentId * @param int $exceptId 如果指定此参数,则排除掉对指定Id的校验 *  * @return boolean */function nameExists($name,$parentId,$exceptId=null){$cond = array('parent_id' =>$parentId,'name'=>$name);if ($exceptId){$cond['id'] = array($exceptId,'!=');}return $this->_dbo->getDbUtils()->findCount($this->tableName,$cond);}    function update(array $node){// 未指定主键if (!isset($node[$this->primaryKey])) return false;$node['updated_at'] = CURRENT_TIMESTAMP;$sql = CoreDbSqlHelper::getUpdateSQL($this->_dbo,$node,$this->primaryKey,$this->tableName);if ($sql){if ($this->_dbo->execute($sql,$node))return $this->_dbo->lastQueryAffectedRows();}return false;}    /**     * 更新节点信息     *     * @param array $node     *     * @return boolean     */    function save(array $node) {        //检验节点名称是否在父节点下是唯一的    if (isset($node['name'])) {    if ($this->_uniqueNameSupport && $this->nameExists($node['name'],$node['parent_id'],$node[$this->primaryKey])){throw new NodeNameExistsException($node['name'],$node[$this->primaryKey]);}    }        unset($node['left_value']);        unset($node['right_value']);        unset($node['parent_id']);        unset($node['deep']);        return $this->update($node);    }    /**     * 删除一个节点及其子节点树     *     * @param array $node     *     * @return boolean     */    function remove(array $node) {    $this->_dbo->startTrans();    $rst = false;        do {    $span = $node['right_value'] - $node['left_value'] + 1;        $sql = "DELETE FROM {$this->tableName} " .               "WHERE left_value >= {$node['left_value']} " .               "AND right_value <= {$node['right_value']}";            $rst = $this->_dbo->execute($sql);        if (!$rst) break;                $sql = "UPDATE {$this->tableName} " .               "SET left_value = left_value - {$span} " .               "WHERE left_value > {$node['right_value']}";        $rst = $this->_dbo->execute($sql);        if (!$rst) break;                        $sql = "UPDATE {$this->tableName} " .               "SET right_value = right_value - {$span} " .               "WHERE right_value > {$node['right_value']}";            $rst = $this->_dbo->execute($sql);//        if (!$rst) break;            } while(false);        $this->_dbo->completeTrans();    return $rst;    }    /**     * 删除一个节点及其子节点树     *     * @param int $nodeId     *     * @return boolean     */    function removeByPkv($nodeId) {    $node = $this->find(array($this->primaryKey=>$nodeId));                if (!$node) {        throw new NodeNotFoundException($nodeId);        }        return $this->remove($node);    }/** * 通过路径查找分类: /图书/IT/技术/PHP * * @param string $name * @return array */function getByName($name){if (empty($name)) return null ;$path = trim($name);if (!preg_match('/^\//',$name)) $name = "/{$name}";if ($name === '/'){$name = $this->_rootNodeName ;$deep = 0;}else {$parts = normalize($path,'/');$deep = count($parts);$name = array_pop($parts);}return $this->find(array('name' => $name ,'deep' => $deep) );}    function findAll($cond, $sort=null, $fields='*'){$sqlCond = CoreDbSqlHelper::parseConditions($this->_dbo,$cond);if ($sqlCond) $sqlCond = "WHERE {$sqlCond}";if ($sort) $sort = "ORDER BY {$sort}";$qfields = CoreDbSqlHelper::qfields($fields,$this->tableName);$sql = "SELECT {$qfields} FROM {$this->tableName} {$sqlCond} {$sort}";return $this->_dbo->getAll($sql);}    /**     * 返回根节点到指定节点路径上的所有节点     *     * 返回的结果不包括“_#_ROOT_NODE_#_”根节点各个节点同级别的其他节点。     * 结果集是一个二维数组,可以用 array_to_tree() 函数转换为层次结构(树型)。     *     * @param array $node     *     * @return array     */    function getPath(array $node, $fields='*') {        $inputarr = array($node['left_value'], $node['right_value']);        $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value < ? AND right_value > ?',$inputarr);        $sort = 'left_value ASC';        $rowset = $this->findAll($cond, $sort, $fields);        if (is_array($rowset)) {            array_shift($rowset);        }        return $rowset;    }        /**     * 取得下层分类到指定的上层分类的完整路径     *      * 返回的结果不包括 $up 节点         * @param array $down     * @param array $up     *      * @return array     */    function getPartPath(array $down, array $up, $fields='*'){        //父分类的left_value小于子分类的left_value;父分类的right_value大于子分类的right_value    $inputarr = array($up['left_value'], $down['left_value'],$down['right_value']);            $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value BETWEEN ? AND ? AND right_value > ?',$inputarr);        $sort = "left_value ASC";        $rowset = $this->findAll($cond, $sort, $fields);    if (is_array($rowset)) {            array_shift($rowset);        }        return $rowset;    }        /**     * 返回指定节点的直接子节点     *     * @param array $node     *     * @return array     */    function getSubNodes(array $node, $fields='*') {    $inputarr = array($node[$this->primaryKey]);            $cond = CoreDbSqlHelper::bind($this->_dbo,'parent_id = ?', $inputarr);        $sort = 'left_value ASC';        return $this->findAll($cond, $sort, $fields);    }    /**     * 返回指定节点为根的整个子节点树     *     * @param array $node     *     * @return array     */    function getSubTree(array $node, $fields='*') {    $inputarr = array($node['left_value'], $node['right_value']);            $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value BETWEEN ? AND ?', $inputarr);        $sort = 'left_value ASC';        return $this->findAll($cond, $sort, $fields);    }    /**     * 获取指定节点同级别的所有节点     *     * @param array $node     *     * @return array     */    function getCurrentLevelNodes(array $node, $fields='*') {        $cond = array('parent_id' => $node['parent_id']);        $sort = 'left_value ASC';        return $this->findAll($cond, $sort, $fields);    }    /**     * 取得所有节点     *     * @return array     */    function getAllNodes($fields='*') {        return parent::findAll('left_value > 1', 'left_value ASC', $fields);    }    /**     * 获取所有顶级节点(既 _#_ROOT_NODE_#_ 的直接子节点)     *     * @return array     */    function getAllTopNodes($fields='*') {        $cond = "parent_id = 0";        $sort = 'left_value ASC';        return $this->findAll($cond, $sort, $fields);    }    /**     * 计算所有子节点的总数     *     * @param array $node     *     * @return int     */    function calcAllChildCount(array $node) {        return intval(($node['right_value'] - $node['left_value'] - 1) / 2);    }}
0 0
原创粉丝点击