php 进阶:实现无限分类

  1.分析

  我们在用 php 制作网站时,分类是很重要的,在分类下面又再分类这第二个分类称为次分类,而现在大多数网站分类只分到第三类:

  第一分类(父分类)-->第二分类(子分类)-->第三分类(孙分类)

  这种亲缘分类越多,程序和数据库的控制就越加的复杂困难.在同一级的分类处理和控制是非常的简单的,因为只需要一个数据库来记载这一级的分类就行了,如:系统,新闻等分类,在这一级上处理是很简单的,但对一个网站来说一级分类是不够的,还需要再分类,如:

  系统-->linux,windows

  新闻-->linux 新闻,windows 新闻

  这样分类就清晰些了,至少让人明白了,系统包括 linux 和 windows,而新闻包括 linux 新闻和 windows 新闻,为了让信息资料更加清晰,于是再继续分类:

  linux-->系统工具,内核,编程语言,开发工具

  ...

  分类到了第三级,信息资料的处理就更清晰了,也就是说为了很清晰地处理资料,分类越详细就越方便,这样即方便处理信息又方便网友目的明确地查找到需要的资料,但随着不断的细化分类,在程序和数据库的控制上就会越来越困难.

  困难一:如何在数据库里处理这些互有关联的亲缘分类?

  困难二:如何用 php 完成这种一目了然的关系?

  这种分级多而细的分类是每个 php 程序员都必须解决的问题,因为制作一个好而出色的网站分类问题是不可避免的,而解决这个问题又是相当复杂的,其中最大的问题就是数据库的分类处理,因为如果数据库处理不当将会带来巨大的工作量甚至是不得不重新规划数据库...

  这并不是夸张,因为很多人在数据库处理上就会采用一级分类建立一个数据库的做法,我当时也是采取这种方法处理分类的,因大多网站都是分到第三级,所以数据库里只需三个分类数据库来进行处理.但是需要继续向下分类时,这种做法的弊端就显露出来了,因为越往下分,工作量,程序量将会巨增..

  我要介绍的这种方法就是如何用一个分类数据库建立无限向下分级的分类方法,用过 windows 的读者都知道 windows 文件夹就可以建立无限分级的目录,可在目录下面继续建立目录,这样没完没了的分下去,Linux 的目录创建也有这种功能,我介绍的这个方法跟这种形式相同.

  2.数据库的规划

  ------------------------------------------------------------

  前面谈到分类的复杂性,因此如何规划数据库便成为了实现无限分类非常重要的一步.

  我曾介绍过论坛的数据库规划,不错论坛能够实现无限的跟接,无限分类便是这种形式的扩展,分类同样是这种子父的关联关系,所以分类的数据库就是如何确立明确这种子父关系,这里面有几个难点.

  1)如何处理各分类的信息存储;

  2)如何处理分类的亲缘关系;

  3)如何处理对信息的查询;

  亲缘关系的数据库处理与论坛的数据库处理类似,这里建个 type 的数据库用来处理分类:

  建立字段:

  id(int):用来记录各分类的自然序号

  uid(int):用来记录该分类的父分类的 id 号

  type(char):类别的名称

  roue_id(varchar):亲缘树,以 :0:2:10:20: 的 id 连接表明亲源关系

  roue_char(varchar):亲缘树,类似 :系统:linux:开发工具:gcc: (这个字段有没有都没关系,为了更方便地了解各亲缘关系当然字符表述比数字表述更直接^o^,不过最好加上这个字段)

  这样一个无限分类的类别表就建立了起来,接下来就需要建立存储信息的数据库,处理查询一个表最方便所以这里建立一个表存储信息 type_message:

  id(int):信息的序号;

  typeid(int):所属类别的 id 号;

  title(varchar):信息标题;

  message(text):信息内容;

  time:信息建立的时间;

  这两个数据表就能够完成无限分类的这个任务了(两个表的辅助字段就没加了,读者可自行加入).

  剩下的任务就全部交由 php 来处理完成.

  3.程序控制

  ------------------------------------------------------------

  实现无限分类这个功能中就属这一步最为复杂辛苦,首先看看程序需要完成的步骤:

  1)创建分类上传;

  2)创建信息上传;

  3)明确显示各分类及其之间的关系;

  4)处理查询功能;

  5)如何处理编辑和删除的功能;

  而这五步中最为困难的就是第五个步骤,因为对分类的编辑和删除涉及到一至性的问题.

  下面我就逐一描述 php 的程序控制:

  1)创建分类上传

  在介绍这个功能前,先介绍一下 explode( ) 这个函数,这是个字串处理函数,用来分解字串的,具体的用法,例:

  分解"0:1:2:3:4"里的数字

  $val='0:1:2:3:4';

  $rid=explode(":",$val);

  经过 explode( ) 函数处理,$val 内的所有数字都分解到 $rid 数组中了,要引用时只需打印:echo '$rid[0],$rid[1],$rid[2]..."; 就行了.explode( ) 函数在整个分类处理中起着非常重要的作用,好现在开始介绍无现分类的程序控制.

  可以假设个总分类 0 ,所有的分类都是它的子孙分类,现在来建立第一个分类'系统',来看看它在数据库的存储形式:

  id | uid | type | rout_id | rout_char

  1 | 0 | 系统 | 0:1 | 系统

  接着又在下面分'Linux':

  id | uid | type | rout_id | rout_char

  2 | 1 | Linux| 0:1:2 | 系统:Linux

  以上就是数据库存储的形式,现在就来完成 php 的代码,这与论坛的代码很相似,我们所要做的就是将分类的 id 放入 uid,而父分类的 uid 就放 0,下面来看看代码:

  <?

  .....

  .....

  //设置默认页

  if (empty($func)) $func=='showtype';

  //设置父分类的 uid

  if (empty($uid)) $uid=0;

  //数据库存储************************************************

  if ($func=='save'):

  $fields = "";

  $values = "";

  if ($id!="") {

  $fields .= ",id";

  $values.=",$id";

  }

  if ($uid!="") {

  $fields .= ",uid";

  $values.=",$uid";

  }

  if ($type!="") {

  $fields .= ",type";

  $values.=",'$type'";

  }

  if ($route_id=="") {

  //取得父分类的 route_id

  if ($uid!=0) {

  $result = mysqlquery("select * from type where id=$uid");

  $route_id=mysql_result($result,0,"route_id");

  } else {

  $routr_id='0';

  }

  $fields .= ",route_id";

  //形成自己的 route_id

  $route_id="$route_id:$id";

  $values.=",'$route_id'";

  }

  //形成自己的 route_char

  if ($route_char!="") {

  $fields .= ",route_char";

  $route_char="$route_char:$type";

  $values.=",'$route_char'";

  } else {

  $fields .= ",route_char";

  $route_char=$type;

  $values.=",'$route_char'";

  }

  $fields = substr($fields,1,strlen($fields)-1);

  $values = substr($values,1,strlen($values)-1);

  $result = mysqlquery("insert into type ($fields) values ($values)");

  ...

  endif; /* end save */

  //分类上传************************************************

  if ($func=='createtype'):

  //取得自己的 id

  $result = mysqlquery("select * from type order by

  id desc");

  $num=mysql_numrows($result);

  if (!empty($num)) {

  $cat = mysql_result($result,0,"id");

  } else {

  $cat=0;

  }

  //判断分类的状态

  if ($uid != 0) {

  $result=mysql_query("select * from type where id=$uid");

  $type=mysql_result($result,0,"type");

  $route_char=mysql_result($result,0,"route_char");

  } else {

  $type='父分类';

  }

  echo "<FORM ACTION="$PHP_SELF?func=save" METHOD=POST>";

  echo "<table>";

  echo "<tr><td>所属类别:$type</td></tr>";

  echo "<tr><td>创建分类:<input type=text name='type' SIZE=10 MAXLENGTH=100></td></tr>";

  echo "<tr><td>";

  $cat=$cat+1;

  echo "<input type=hidden name=id value='$cat'>";

  echo "<input type=hidden name=uid value='$uid'>";

  echo "<input type=hidden name=route_char value='$route_char'>";

  echo "<INPUT TYPE=submit NAME='Save' VALUE='保存'></td></tr>";

  echo "</table>";

  echo "</form>";

  endif; /* end createtype */

  //显示分类************************************************

  if ($func=='showtype'):

  echo "<table>";

  //判断分类的状态

  if ($uid!=0) {

  $result=mysql_query("select * from type where id=$uid");

  $type=mysql_result($result,0,"type");

  } else {

  $type='父分类';

  }

  echo "<tr><td><a href='$php_self?func=createtype&uid=$uid'>创建分类</a></td></tr>";

  echo "<tr><td>$type</td></tr>";

  $result=mysql_query("select * from type where uid=$uid");

  $num=mysql_numrows($result);

  if (!empty($num)) {

  for ($i=0;$i<$num;$i++) {

  $id=mysql_result($result,$i,"id");

  $type=mysql_result($result,$i,"type");

  echo "<tr><td>";

  echo "<a href='$php_self?func=showtype&uid=$id'>$type</a>";

  echo "</td></tr>";

  }

  }

  echo "</table>";

  endif; /* end showtype */

  .....

  .....

  ?>

  以上的程序便完成了无限分类的基本创建,存储和显示,接着就是完善分类创建功能的各个部分了.

  4.路径跟踪

  ------------------------------------------------------------

  前面已经介绍过了分类的创建实现方法,在分类表里记载了 rout_id 和 rout_char 这两个存储分类路径的信息,在不做任何处理的情况下,程序只能够顺序下到最底层的分类而无法倒退(当然可利用浏览器的 back 键倒退,但这对程序来说是不完整的),因此必须将 rout_id 和 rout_char 的信息分解出来完成实在的路径指示.

  具体的做法,假如数据库记载了这么一条分类信息:

  id:4

  uid:2

  type:开发工具

  rout_id:0:1:2:4

  rout_char:系统:linux:开发工具

  当程序走到分类'开发工具'上时,除了要求显示路径信息外还要求能够去到路径上的任一分类中,该怎么做能?这里就需要用到 explode() 函数了.因为 rout_id 和 rout_char 是对应关系的,所以可将它们分解:

  $path=explode(":",$rout_id);

  $path_gb=explode(":",$rout_char);

  这时所有分类信息都被分解了,现在要做的就是以链接的方式还原路径信息:

  for ($i=0;;$i++) {

  $a=$i+1;

  echo "<a

  href=$php_self?func=showtype&uid=",$path[$a],">",$path_gb[$i],"</a>:";

  if (empty($path_gb[$i])) {

  break;

  }

  }

  上面这段代码就实现了加链接还原路径的功能,因为实现的是无限分类,因此是没有上限的,所以在 for($i=0;;$i++) 里没有范围限制,而设置循环退出的条件是 $path_gb[$i] 中的值为空,将这段代码插入类别显示版面的程序块内就行了:

  <?

  .....

  .....

  //显示分类************************************************

  if ($func=='showtype'):

  echo "<table>";

  //判断分类的状态

  if ($uid!=0) {

  $result=mysql_query("select * from type where id=$uid");

  $type=mysql_result($result,0,"type");

  //******** 新加入的代码 ***************

  $rout_id=mysql_result($result,0,"rout_id");

  $rout_char=mysql_result($result,0,"rout_char");

  $path=explode(":",$rout_id);

  $path_gb=explode(":",$rout_char);

  echo "<tr><td>";

  for ($i=0;;$i++) {

  $a=$i+1;

  echo "<a

  href=$php_self?func=showtype&uid=",$path[$a],">",$path_gb[$i],"</a>:";

  if (empty($path_gb[$i])) {

  break;

  }

  }

  echo "</td></tr>";

  //******** end ***********************

  } else {

  $type='父分类';

  }

  echo "<tr><td><a href='$php_self?func=createtype&uid=$uid'>创建分类</a></td></tr>";

  echo "<tr><td>$type</td></tr>";

  $result=mysql_query("select * from type where uid=$uid");

  $num=mysql_numrows($result);

  if (!empty($num)) {

  for ($i=0;$i<$num;$i++) {

  $id=mysql_result($result,$i,"id");

  $type=mysql_result($result,$i,"type");

  echo "<tr><td>";

  echo "<a href='$php_self?func=showtype&uid=$id'>$type</a>";

  echo "</td></tr>";

  }

  }

  echo "</table>";

  endif; /* end showtype */

  .....

  .....

  ?>

  完成这个功能块后,就可继续分类信息的显示实现了...