用户
 找回密码
 入住 CI 中国社区
搜索
查看: 13882|回复: 16
收起左侧

[讨论/交流] CI无限分类的实现方式(非递归)

  [复制链接]
发表于 2010-10-18 10:19:05 | 显示全部楼层 |阅读模式
本帖最后由 spt119 于 2010-10-18 14:19 编辑

前几日帮朋友写了个小程序,其中有用到PHP无限分类。关于无限分类,有很多实现的方式,但多基于对数据库的多次操作,而且是递归查询。虽然对于小型项目而言,递归与否以及查询数据库的次数对效率的影响是可以无视的,但还是本着精益求精的想法,重新理顺思路写了一个。
这个无限分类程序基于纯PHP操作,没有javascript参与。你完全可以在代码的基础上重新开发一下即可。多了就不白话了,直接上代码:
1.数据库
SQL复制代码
CREATE TABLE IF NOT EXISTS `tuan_cate` (
  `cid` INT(11) NOT NULL AUTO_INCREMENT,
  `fatherid` INT(11) NOT NULL DEFAULT '0',
  `catename` VARCHAR(50) DEFAULT NULL,
  `lineid` INT(11) NOT NULL DEFAULT '0',
  `content` VARCHAR(1000) DEFAULT NULL,
  `path` VARCHAR(32) DEFAULT NULL,
  `rank` INT(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`cid`),
  KEY `catename` (`catename`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=71 ;
复制代码

2.控制器
PHP复制代码
 
function __construct(){
  $this->cate = $this->cate_model->get();
  $data ['cate'] = $this->cate;
}
function update()
{
if( empty($cid) )
{
        $cid = $this->input->post('cid',TRUE);
        $path = $this->input->post('father',TRUE);
        $sql = array(
          'catename'    => $this->input->post('catename',TRUE),
          'lineid'        => $this->input->post('lineid',TRUE),
          'content'       => htmlspecialchars($this->input->post('content',true))
        );
      }
      else
      {
        $this->cateinfo = $this->cate_model->get_one($cid);
        echo 'path = '.$path.' - this->cateinfo->path = '.$this->cateinfo->path;
        if( $this->cateinfo->path != $path )
        {
          $path_change = 1;
        }
        else
        {
           $path_change = 0;
        }
       
      }
     
      if( empty( $cid ) )
          {//insert new cate
                    $this->cate_model->insert($sql,$path);
          }
          else
          {//modify the exists cate
        $this->cate_model->update($cid,$sql,$path,$path_change);
          }
      $this->dblog_model->add();
      //redirect('back/tuan_cate','refresh');
        }
}
 
复制代码

3.模型
PHP复制代码
  function get()
  {
    $this->db->order_by('path','asc');
    $query = $this->db->get('cate');
    if($query->num_rows() > 0)
    {
      return $query->result_array();
    }
  }
  function get_one($cid)
  {
    $this->db->where('cid',$cid);
    $query = $this->db->get('cate');
    if($query->num_rows > 0)
    {
      return $query->row();
    }
  }
  function insert($sql,$path)
  {
    $this->db->insert('cate',$sql);
    $new_id = $this->db->insert_id();
    $new_path = $path.','.$new_id;
    $p = explode(',',$path);
    rsort($p);
    $fatherid = $p[0];
    $rank = explode(',',$new_path);
    $this->db->where('cid',$new_id);
    $this->db->update('cate',array( 'fatherid' => $fatherid,'path' => $new_path,'rank' => count($rank) ));
  }
 
  function update($cid,$sql,$path,$path_change)
  {
   
    if($path_change == 1)
    {
      $new_path = $path.','.$cid;
      $p = explode(',',$path);
      rsort($p);
      $fatherid = $p[0];
      $rank = explode(',',$new_path);
      $this->db->where('cid',$cid);
      $this->db->update('cate',array( 'fatherid' => $fatherid,'path' => $new_path,'rank' => count($rank) ));
    }
    $this->db->where('cid',$cid);
    $this->db->update('cate',$sql);
  }
 
复制代码

4.视图
以下是分类显示部分;
PHP复制代码
<?php foreach((array) $cate as $key => $c){?>
     
      <tr>
          <td>
        <?php echo '┣';
              if( $c['rank'] > 0 )
              {
                for( $i=1;$i<=$c['rank'];$i++ )
                {
                  if($i > 0 AND $i < $c['rank']) {echo '━';}
                  if($i >= $c['rank']) {echo '━';}
                }
              }
              echo ' <span id=red>'.$c['rank'].'级</span> '.$c['catename'];
        ?>
        </td>
          <td><?php echo $c['catename'];?></td>
        <td><?php echo $c['lineid'];?></td>
        <td><?php echo $c['content'];?></td>
        <td><a id="add1" href="<?php echo site_url('back/tuan_cate/update/'.$c['cid']);?>#add">修改</a>&nbsp;|&nbsp;<a href="<?php echo site_url('back/tuan_cate/delete/'.$c['cid']);?>">删除</a></td>
      </tr>
      <?php }unset ($c);?>
复制代码

以下是类的添加和修改部分:
PHP复制代码
 
<select name="father">
            <?php foreach( (array)$cate as $key => $c )
                  {
                    echo "<option value=\"".$c['path']."\"";
                    if( ! empty($cid) )
                    {
                      if( $c['cid'] == $modify->cid ) echo ' selected';
                    }
                    echo '>';
                    echo '┣';
                    if( $c['rank'] > 0 )
                    {
                      for( $i=0;$i<=$c['rank'];$i++ )
                      {
                        if($i > 0 AND $i < $c['rank']) {echo '━';}
                        if($i >= $c['rank']) {echo '━';}
                      }
                    }
                    echo " ".$c['rank']."级 ".$c['catename']."</option>\n";
                  }
                 
                 
            ?>
           
            </select>
复制代码

评分

参与人数 1威望 +5 收起 理由
Hex + 5 精品文章

查看全部评分

 楼主| 发表于 2014-7-20 22:09:35 | 显示全部楼层
回16楼:
这个设计大概好像是在11年冬天搞的,用处就是针对类别的无限分类的处理。一般而言,一个项目的类别设定不会超过1000个,而1000行数据装入数组在内存中“折腾”,以现在服务器CPU的多核心而言,无所谓效率与否。最重要的,我觉得这是一次读数据库,相比来回读数据库的设计而言,我更倾向于在数组中充分利用PHP数组来实现效果。
所以,16楼所谓的“查询效率”,完全不知所谓。
 楼主| 发表于 2010-10-18 10:30:06 | 显示全部楼层
在一楼解释一下。
一、数据库中,fatherid用来存放父类的id。因为是无限分类,这个父类可以是任意已经存在的类别(无论是几级)的id。path是当前类的所有隶属关系。如果是一级类,path = id。如果是二级类,path = 父类id+自己的id。如果是三级类,path=爷爷类id+父亲类id+儿子类id。依次类推。rank是当前目录的级别,比如一级、二级、三级等。这个字段主要用来控制制表符的形式和长度。如果用空格或其他方式,这个字段可以取消。
二、视图中,在select的option的value中,直接赋值path字段值。如果判断cid为空,就是新加类,如果非空,就是修改现有类。模型中使用foreach循环从数据库取出的分类表的所有类别,并利用rank字段来判断类的级别以及制表符的形状。
三、控制器中,通过接收的select值并把这个值传递到模型中。如果cid非空,则设定了一个path_change变量。如果传递过来的path=数据库中的path,则不修改类的级别和隶属关系,仅修改类的其他属性信息。如果传递过来的path不等于数据库中的path,则需要更改类的所有信息。
四、模型。模型中使用了一些数组。其实这部分工作应该设定一个function函数来解决,不过这个项目较小,所以也就没有再“费事”。大家可以在原理的基础上扩展。
总结:只搜索一次数据库,非递归方式实现分类的排序。
发表于 2010-10-18 11:56:18 | 显示全部楼层
在一楼解释一下。
一、数据库中,fatherid用来存放父类的id。因为是无限分类,这个父类可以是任意已经存在的类别(无论是几级)的id。path是当前类的所有隶属关系。如果是一级类,path = id。如果是二级类,path = 父类id+自己的id。如果是三级类,path=爷爷类id+父亲类id+儿子类id。依次类推。rank是当前目录的级别,比如一级、二级、三级等。这个字段主要用来控制制表符的形式和长度。如果用空格或其他方式,这个字段可以取消。
二、视图中,在select的option的value中,直接赋值path字段值。如果判断cid为空,就是新加类,如果非空,就是修改现有类。模型中使用foreach循环从数据库取出的分类表的所有类别,并利用rank字段来判断类的级别以及制表符的形状。
三、控制器中,通过接收的select值并把这个值传递到模型中。如果cid非空,则设定了一个path_change变量。如果传递过来的path=数据库中的path,则不修改类的级别和隶属关系,仅修改类的其他属性信息。如果传递过来的path不等于数据库中的path,则需要更改类的所有信息。
四、模型。模型中使用了一些数组。其实这部分工作应该设定一个function函数来解决,不过这个项目较小,所以也就没有再“费事”。大家可以在原理的基础上扩展。
总结:只搜索一次数据库,非递归方式实现分类的排序。

俺是新手来新手区学习的..多谢回答
发表于 2010-10-25 16:04:31 | 显示全部楼层
收藏了,感谢分享
发表于 2010-11-24 16:57:21 | 显示全部楼层
不错,参考了
发表于 2010-11-25 15:45:05 | 显示全部楼层
完全不知所云
发表于 2010-11-25 23:12:20 | 显示全部楼层
这种方法网上看到过
不错
发表于 2010-12-17 12:58:35 | 显示全部楼层
正考虑将管理后台转到CI,
初入门,确实花不起时间,能否搞成一个下载呢?
发表于 2010-12-20 22:43:25 | 显示全部楼层
谢谢分享,收藏啦,哈哈哈。
发表于 2010-12-23 11:13:48 | 显示全部楼层
$this->dblog_model->add();
?这是干啥用?
有点粗糙,思想达到了....

本版积分规则