当前位置:首页 > PHP教程 > php应用 > 列表

PHP实现产品列表分类筛选与排序的项目实例

发布:smiling 来源: PHP粉丝网  添加日期:2015-12-10 15:03:25 浏览: 评论:0 

在电子商务网站中,产品列表页的分类筛选以及按照用户的意向选择排序几乎都有,比如淘宝。本文我们来分享用php实现这一功能的实例。

一、简单的单条件查询

工作都是从简单的开始,先从最简单的单表查询开始,这个一般用在首页以及一些比较独立的页面,只需要查找几个符合条件的产品展示出来即可,可以使用分页或者不使用分页。下面这个是产品控制器 ProductController 中的一个函数,用于简单的查询,比如199元专区就可以使用 getTypeSimPro('price=199');

  1. /**简单的筛选条件分类产品,单表查询 
  2.  * @param string $sql 单表查询的SQL 
  3.  * @param int $countPerPage=16 每页商品数 
  4.  * @param string $orderBy='salseF DESC' 排序 默认销量阈值 
  5.  * @return array $res 产品二维数组 
  6.  */ 
  7. function getTypeSimPro($sql$countPerPage = 16, $orderBy='salesF DESC'){ 
  8.     //$sql = "SELECT ProductId,name,mainPic,priceN,priceVIP,isNew,isHot,sales FROM product WHERE ".$sql; 
  9.     $productM = M('product');    // 实例化Data数据对象        
  10.     $where = $sql ? 'onSale=1 AND '.$sql : 'onSale=1'
  11. //phpfensi.com 
  12.     $tempSQL = $productM->field('ProductId,name,mainPic,priceN,priceVIP,isNew,isHot,sales,salesF'
  13.                          ->where( $where )                         
  14.                         ->order( $orderBy ); 
  15.                          
  16. //    $res = $this->executeTempSQL($tempSQL, $countPerPage); 
  17.     $res = $tempSQL->select();    //演示不使用分页,直接返回结果集 
  18.     return $res

二、使用分页

由于Thinkphp的自带Page分页类有些不太好用,所以我进行了一点小改造,可以进行传递配置参数修改页码显示的方式。这里的主要实现逻辑是:

1、利用同一个临时数据库对象 $tempSQL ,使计数和查询结果的条件保持一致,注意这里使用了对象克隆,因为TP中,一个Model执行完操作后会被初始化成原始的Model对象,参见 TP手册连贯操作说明>> 。

2、$_GET['p']是Page类默认的辨别当前页码的参数。Page类尤其里面的 show() 函数是经过我改造的,可以传递定制化页码导航栏参数。不定制也可以,就是页码导航有点太长。

3、这里的 count() 在后面多表查询的时候是有BUG的,后面再说。

  1. /** 
  2.  * 执行分类和搜索中的SQL对象 
  3.  * @param TP.Model $tempSQL Thinkphp的Model对象 
  4.  * @param int $countPerPage=16 每页的产品数 
  5.  * @return array $res['nowP']当前页数  $res['totalP']总产品数  $res['links']分页栏HTML     $res['productList']产品二维数组 
  6.  * */ 
  7. protected function executeTempSQL( $tempSQL$countPerPage=16 ){ 
  8.     $tempSQL2 = clone $tempSQL;        //对象复制,否则调用一次后第二次会被初始化成原始的M对象 
  9. //    print_r($tempSQL); 
  10.     $count = $tempSQL->count(); // 查询满足要求的总记录数,这里在多表查询时一定要以产品编号为限制条件 
  11. //  var_dump($count); 
  12. //    var_dump($tempSQL); 
  13.  
  14.     $nowPage = isset($_GET['p'])?$_GET['p']:1;  //当前页         
  15.     import('ORG.Util.Page');// 导入分页类 
  16.     $Page = new \Think\Page($count,$countPerPage);        // 实例化分页类 传入总记录数,每页数 
  17.     $list = $tempSQL2->page($nowPage.','.$Page->listRows)->select();        //查询结果集         
  18. //    var_dump($list); 
  19.          
  20.     //分页导航的定制 
  21.     $showConfig = array
  22.         'first'  => '首页'
  23.         'prev'   => '上一页'
  24.         'next'   => '下一页',     
  25. //        'last'   => '尾页',    //这个不行 
  26.         'rollPage' => 5,        //最多显示5页导航 
  27.     ); 
  28.     $links = $Page->show( $showConfig );            // 分页显示输出 
  29.     //var_dump($links); 
  30.     //var_dump($list); 
  31.     $res['nowP'] = $nowPage
  32.     $res['totalP'] = $count
  33.     $res['links'] = $links;            //分页输出 
  34.     $res['productList'] = $list;    //数据集 
  35.     return $res

三、多表查询功能概览

其中的数据库设计为:

product表:ProductId-产品ID、name-产品名、sort1-一级分类、sort2-品牌分类、price-价格、onSale-上下架……等等

reserve表:ProductId-产品ID、color-颜色、size-尺码、reserve-库存

tagpro表:Id-自增没实际用途、tagId-标签ID、ProductId-产品ID

tag表:Id-标签ID、tag_name-标签名

标签与产品是多对多的关系,在上面展示的分类和搜索中,黑色导航栏、性别以及以后可能扩展的筛选项为标签联表查询,尺码为库存表联表查询.

四、SearchController控制器

定义了一个Search控制器,里面有下面几个方法:

function index() 方法是根据上面页面中的筛选选项拼装相应的SQL语句的,提交到ProductController去筛选出相关的产品;

function getCutURL($getKey, $CtrlName=CONTROLLER_NAME) 是为了给页面生成一系列切除了指定get值的URL地址的;

function pageCheck() 如果改变了筛选条件,则去除页码参数,回到从第一页开始;

在我的项目规划中IndexController负责页面的显示,所以IndexController中的 search() 方法则负责搜索页面的展示,代码如下:

  1. function search(){         
  2. $searchC = A('search'); 
  3. $res = $searchC->index();  
  4. $URLArr['type2URL'] = $searchC->getCutURL('type2'); //取消选择分类的URL 
  5. $URLArr['brandURL'] = $searchC->getCutURL('brand'); //取消选择品牌的URL 
  6. $URLArr['peopleURL'] = $searchC->getCutURL('people');//取消选择性别人群的URL
  7. $URLArr['sizeURL'] = $searchC->getCutURL('size'); //取消选择尺码的URL 
  8. $URLArr['priceURL'] = $searchC->getCutURL('price'); //取消选择价格区间的URL         
  9. $URLArr['keyword'] = $searchC->getCutURL('keyword'); //取消搜索关键字的URL      
  10. $URLArr['orderbyURL'] = $searchC->getCutURL('orderby');        //orderby按钮的URL前部分 
  11.          
  12. $this->assign('URLArr'$URLArr)         
  13.   ->assign ( 'productArr'$res['productList'] ) 
  14.   ->assign( 'totalNum'$res['totalP'] ) 
  15.   ->assign( 'pageinfo'$res['links']) 
  16.   ->display(); 

五、两表多次查询

因为产品与标签是多对多的关系,所以有一种需求是:查询同时拥有两个标签一个产品,姑且设读取列为*即全部列。

一开始想到的SQL语句是这个样子的

SELECT * FROM product p INNER JOIN tagpro ON tagpro.ProductId = p.ProductId INNER JOIN tagpro ON tagpro.ProductId = p.ProductId WHERE onSale=1 AND tagpro.tagId=46 AND tagpro.tagId=40;

然而这条语句并没有执行成功,而是报错 Not unique table/alias: 'tagpro' ,意思是说两次INNER JOIN的表是同一个表/表别名,所以不行。所以我就试着把一个INNER JOIN删掉,然后再看是可以执行了,但是却是没有查到任何结果。到这里,我差点就要骂SQL不够智能了,明明是该产品在tagpro表中有tagId等于46也有tagId等于40,为什么你要理解成了 tagId同时等于46和40呢?

SELECT * FROM product p INNER JOIN tagpro ON tagpro.ProductId = p.ProductId WHERE onSale=1 AND tagpro.tagId=46 AND tagpro.tagId=40;

骂也没用,只能再想办法,相信这种事情很多人都遇到,SQL肯定有办法解决这种问题,后来尝试了一下这种写法,把同一个表分别用了两个不同的别名,然后终于能查出来了.

SELECT * FROM product p INNER JOIN tagpro a ON a.ProductId = p.ProductId INNER JOIN tagpro b ON b.ProductId = p.ProductId WHERE onSale=1 AND a.tagId=46 AND b.tagId=40;

六、产品控制器中的SQL查询函数

前面说了,Search控制器中的index()方法负责拼接SQL语句,提交到 Product控制器中进行产品的查询,现在在Product控制器中新建一个 getSearchPro() 方法,参考原来简单查询中的做法,另外加入JOIN的处理。这里其实就是把 where拼接起来, 把 join 拼接起来。原始的where和join的生成在Search控制器的index()中。

  1. /**根据筛选条件查找分类产品,多表查询     //默认每页16    //排序为销售阈值 
  2.      * @param string $sql 单表查询的SQL 
  3.      * @param int $countPerPage=16 每页商品数 
  4.      * @param string $orderBy='salseF DESC' 销量阈值 
  5.      * @param array $joinConfig=NULL 多表查询时  
  6.      *         $joinConfig['joinTable']为联合表名二维数组,每一列遍历为 $joinTableL 
  7.      *                 $joinTableL[name]为真实表名  $joinTableL[asname]为as表名 
  8.      *         $joinConfig['where']为附加查询条件as表名的字段=条件  $joinTableL[asname].size=$size; 
  9.      * @return array $res['nowP']当前页数  $res['totalP']总产品数  $res['links']分页栏HTML $res['productList']产品二维数组 
  10.      */     
  11.     function getSearchPro($sql$countPerPage = 16, $orderBy='salesF DESC'$joinConfig=NULL){         
  12.         $productM = M('product')->alias('p');    // 实例化Data数据对象 
  13.         $where = $sql ? 'p.onSale=1 AND '.$sql : 'p.onSale=1'
  14.  
  15.         $joinTableArr = $joinConfig['joinTable'];    //要JOIN的表名 
  16.         $joinWhereArr = $joinConfig['where'];        //要筛选的格外条件 
  17.         foreach$joinWhereArr as $joinLine ){ 
  18.             $where .= ' AND '.$joinLine
  19.         } 
  20.          
  21.         $tempSQL = $productM->distinct(true) 
  22.                         ->field('p.ProductId,p.name,p.mainPic,p.priceN,p.priceVIP,p.isNew,p.isHot,p.sales,p.salesF'
  23.                         ->where( $where ) 
  24.                         ->order( $orderBy ); 
  25.         //处理JOIN 
  26.         foreach$joinTableArr as $joinTableL){ 
  27.             $tempSQL = $tempSQL->join("$joinTableL[name] AS $joinTableL[asname] ON $joinTableL[asname].ProductId = p.ProductId"); 
  28.         } 
  29.      
  30.         $res = $this->executeTempSQL($tempSQL$countPerPage'p.ProductId'); 
  31.         return $res
  32.     } 

注意最后的 $res = $this->executeTempSQL($tempSQL,$countPerPage, 'p.ProductId'); 最后比之前的函数多了一个参数,因为前文提到的 executeTempSQL()方法中的 $count = $tempSQL->count();  改为了  $count = $tempSQL->count('DISTINCT '.$countCond); 否则在多表查询时计数会出现count的数量比实际查到的结果条数多的情况。 这里的executeTempSQL()后面新增的参数为 $countCond,默认值为'ProductId',以便单表查询时不必填写这个无相紧要的参数。

七、Search控制器,筛选项转换成SQL拼接

index()函数:生成查询的SQL语句段,逻辑是:

1、根据 get 的参数,分别依次进行筛选/排序处理;

2、只在product表中产生where条件的,以一次查询加 简单where SQL拼接的方式处理;

3、多表联合并在其它表有 where条件的,以 join 数组的形式提交给产品控制器统一拼接处理;

4、这个是目前现行的方案,以后还要再优化的;

  1. //搜索入口 
  2.     function index( $defaultTag=NULL ){ 
  3.         //如果改变了筛选条件,则去除页码参数 
  4.         $this->pageCheck(); 
  5.  
  6.         //********处理筛选********************************** 
  7.         $type2 = I('get.type2');        // type2: 篮球鞋、跑步鞋…… 
  8.         $brand = I('get.brand');        // brand: 阿迪、匹克、李宁…… 
  9.         $people = I('get.people');        // people: 男、女、中性…… 
  10.         $size = I('get.size');            // size: 35~46、S~L…… 
  11.         $price = I('get.price');        // price: 小于99、100~199…… 
  12.         $tag = I('get.tag');            // tagsId: 限时、热销、性价比…… 
  13.         $keyword = trim( I('get.keyword') );// keyword: 搜索关键字…… 
  14.         $orderby = I('get.orderby'); 
  15.          
  16.         $joinTableNameIndex = 0;    //join所用的表替代名称后缀,为了两个表多次联合查询而设计所有的join表as tb0、tb1... 
  17.         //例如 SELECT * FROM product p INNER JOIN tagpro tb0 ON tb0.ProductId = p.ProductId INNER JOIN tagpro tb1 ON tb1.ProductId = p.ProductId WHERE onSale=1 AND tb0.tagId=46 AND tb1.tagId=40; 
  18.          
  19.         $sql = ''
  20.         //二级分类: /type2/篮球鞋 
  21.         if$type2 ){ 
  22.             $type2Id = M()->query("SELECT Id FROM sort WHERE sortName='$type2'"); 
  23.             $type2Id = $type2Id[0]['Id']; 
  24.             $thesql = "p.sort_secType='$type2Id'"
  25.             $sql = $sql ? $sql.' AND '.$thesql : $thesql
  26.         } 
  27.          
  28.         //品牌筛选: /brand/李宁 
  29.         if$brand ){ 
  30.             $brandId = M()->query("SELECT Id FROM sort WHERE sortName='$brand'"); 
  31.             $brandId = $brandId[0]['Id']; 
  32.             $thesql = "(p.sort_brand='$brandId' OR p.name LIKE '%$brand%')";    //这里除了品牌分类匹配外还根据商品名称模糊匹配 
  33.             $sql = $sql ? $sql.' AND '.$thesql : $thesql
  34.         } 
  35.  
  36.         //性别: /people/男性 /people/女性 /people/中性 
  37.         if$people ){ 
  38.             //人群标签ID 
  39.             $tagsPeopleId = M('tags')->where("tag_name='$people'")->field('Id')->select(); 
  40.             $tagsPeopleId = $tagsPeopleId[0]['Id']; 
  41.              
  42.             $join['joinTable'][] = array('name'=>'tagpro''asname'=>'tb'.$joinTableNameIndex ); 
  43.             $join['where'][] = 'tb'.$joinTableNameIndex.'.tagId='.$tagsPeopleId ; 
  44.             $joinTableNameIndex++; 
  45. //            $sql = $sql ? $sql.' AND '.$thesql : $thesql; 
  46.         } 
  47.          
  48.         //营销标签(与人群标签一样处理逻辑) 
  49.         if$tag ){ 
  50.             $tagsId = M('tags')->where("tag_name='$tag'")->field('Id')->select(); 
  51.             $tagsId = $tagsId[0]['Id']; 
  52.             $join['joinTable'][] = array('name'=>'tagpro''asname'=>'tb'.$joinTableNameIndex ); 
  53.             $join['where'][] = 'tb'.$joinTableNameIndex.'.tagId='.$tagsId ; 
  54.             $joinTableNameIndex++; 
  55. //            $sql = $sql ? $sql.' AND '.$thesql : $thesql;             
  56.         } 
  57.          
  58.         //尺码筛选: /size/35 
  59.         if$size ){ 
  60.             $join['joinTable'][] = array('name'=>'reserve''asname'=>'tb'.$joinTableNameIndex ); 
  61.             $join['where'][] = 'tb'.$joinTableNameIndex.'.size='.$size ; 
  62.             $joinTableNameIndex++; 
  63. //            $join['joinTable'][] = 'reserve'; 
  64. //            $join['where'][] = 'reserve.size='.$size ; 
  65. //            $sql = $sql ? $sql.' AND '.$thesql : $thesql; 
  66.         } 
  67.          
  68.          
  69.         //价格筛选: /price/100~199 /price/大于1999  /price/小于99 
  70.         if$price ){ 
  71. //            var_dump( $price ); 
  72.             if( preg_match('/~/'$price) ){                //    echo '介于'; 
  73.                 $priceArr = explode('~'$price); 
  74.                 $thesql = "p.priceVIP BETWEEN '$priceArr[0]' AND '$priceArr[1]'";                 
  75.             }elseif( preg_match('/大于/'$price) ){            //    echo '大于'; 
  76.                 $priceArr = explode('大于'$price); 
  77.                 $thesql = "p.priceVIP>'$priceArr[1]'";     
  78.             }elseif( preg_match('/小于/'$price) ){            //    echo '小于'; 
  79.                 $priceArr = explode('小于'$price); 
  80.                 $thesql = "p.priceVIP<'$priceArr[1]'"
  81.             }             
  82.             $sql = $sql ? $sql.' AND '.$thesql : $thesql
  83.         } 
  84.          
  85.         //搜索关键字 
  86.         if$keyword ){ 
  87.             $thesql = "(p.ProductId LIKE '%$keyword%' OR p.name LIKE '%$keyword%')"
  88.             $sql = $sql ? $sql.' AND '.$thesql : $thesql
  89.         } 
  90.          
  91.         //排序 
  92.         if$orderby ){ 
  93.             if$orderby == 'default'){ 
  94.                 $orderbySQL = NULL; 
  95.             }elseif$orderby == 'priceVIPa' ){ 
  96.                 $orderbySQL = 'priceVIP ASC'
  97.             }elseif$orderby == 'priceVIPd' ){ 
  98.                 $orderbySQL = 'priceVIP DESC'
  99.             }else
  100.                 $orderbySQL = $orderby.' DESC'
  101.             } //phpfensi.com 
  102.         } 
  103.          
  104.     //*******************************************     
  105.         $productC = A('DataProduct'); 
  106.         $res = $productC->getSearchPro($sql,20,$orderbySQL,$join); 
  107. //    var_dump($res); 
  108.         return $res
  109.     } 

Search 控制器下的另外两个函数如下:

  1. /** 
  2.  * 如果改变了筛选条件,则去除页码参数,回到从第一页开始 
  3.   * 实现原理:如果存在p参数且不是最后一个参数时,则认为是修改了筛选条件 
  4.  * 这里有一点BUG,多项选择再翻页时、取消一个选项并不会回到第一页(因为p参数还是在最后) 
  5.  */ 
  6. function pageCheck(){ 
  7. //        var_dump( $_SERVER['HTTP_REFERER'] ); 
  8. //        var_dump( $_GET ); 
  9.     $getKeyArr = array_keys$_GET ); //var_dump($getKeyArr); 
  10.     $pKeyIndex = array_search('p'$getKeyArr); 
  11. //        var_dump($pKeyIndex); 
  12.     $arrL = sizeof($getKeyArr); //var_dump($getKeyArr); 
  13.      
  14.     if$pKeyIndex!==FALSE && $pKeyIndex+1 < $arrL ){    //p参数如果不是在最后则为更改了筛选条件 
  15.         $cutPurl = $this->getCutURL('p'); 
  16.         redirect( $cutPurl ); 
  17.     } 
  18.      
  19.  
  20. /** 
  21.  * 获得切除了指定get值的URL 
  22.  * @param string $getKey 要去除的get键 
  23.  * @param string $CtrlName 控制器名,默认为页面URL中的控制器名 
  24.  * @return string 不含http://域名 的URL,可直接用于前端输出 
  25.  * */ 
  26. function getCutURL($getKey$CtrlName=CONTROLLER_NAME){ 
  27.     $getStr =''
  28.     $getArr = I('get.'); 
  29.     unset($getArr[$getKey]); 
  30.     foreach$getArr as $getKey=>$getVal){ 
  31.         $getStr .= '/'.$getKey.'/'.$getVal
  32.     } 
  33. //  var_dump($getStr); 
  34.     $thisURL = rtrim( U("$CtrlName/search"), 'html'); 
  35.     $thisURL = rtrim($thisURL'.');    //这两句合并起来会出问题,把seach中的h也去掉了 
  36.     return $thisURL.$getStr;         
  37. }

Tags: PHP产品列表 PHP分类筛选

分享到: