ThinkPHP6.0模型关联自定义分页实现 多对多关联分页

翻遍ThinkPHP6官方手册为找到使用多对多关联,简单实现实现模型关联分页,当然有待优化,供大家学习参考。
解决场景是tag标签查询所有的文章news,针对news进行分页,由于标签和文章是多对多的关系,无法简单实现分页,采用先查出分页数据,再查出总数量,最后自己构建分页类实现。具体如下:

  1. 编写文章路由
<?php
use think\facade\Route;
//文章标签路由
Route::get('tag/:name/:page', 'Tag/index')->name('tag.index')->ext('html');
  1. 控制器查询获取分页数据
<?php
declare (strict_types=1);

namespace app\controller;
use app\admin\model\News;
use app\admin\model\Tags;
use app\common\Bootstrap;

class Tag extends IndexBase
{
    public function index()
    {
        $data=$this->request->param();
        $tag_name=$data['name'];
        $page=(int)$data['page'];
        $listRows=1;//每页显示数量
        $tag=Tags::where('name',$tag_name)->with(array('news'=>function($query)use($page,$listRows){
            $query->with('newsCategory')->page($page,$listRows);
        }))->find();//模型关联查询数据
        $list = Tags::where('name',$tag_name)->withCount('news')->find();
        $results=$tag->news->toArray();//当前页数据结果集合
        $total=$list->news_count;//关联查询统计数量
        $news=new Bootstrap($results, $listRows, $page, $total,false,array('path'=>"/tag/{$tag_name}/1.html"));
        $tag->news=$news;
        return view('index',compact('tag'));
        );
    }
}

相关模型代码News 模型

<?php
namespace app\admin\model;

class News extends Common
{
    protected $autoWriteTimestamp = true;// 开启自动写入时间戳字段

    /*
     * 定义了修改器之后会在下列情况下触发:
     * 模型对象赋值;
     * 调用模型的data方法,并且第二个参数传入true;
     * 调用模型的save方法,并且传入数据;
     * 显式调用模型的setAttr方法;
     * 定义设置文章的描述,若为空直接截取内容前面的文字
     */
    public function setAbstractAttr($abstract, $news)
    {
        $abstract=str_replace(' ','',$abstract);
        if (empty($abstract)){
            return mb_substr(strip_tags(htmlspecialchars_decode($news['content'])), 0, 100, 'utf-8');
        }else{
            return $abstract;
        }
    }

    //
    public function tags()
    {
        return $this->belongsToMany('Tags', '\\app\\admin\\model\\NewsTags', 'tags_id','news_id');
    }
    public function newsCategory()
    {
        return $this->belongsTo('NewsCategory', 'news_category_id', 'id');
    }
}

相关模型代码Tags 模型

<?php
namespace app\admin\model;

class Tags extends Common
{
    protected $autoWriteTimestamp = true;
    protected $insert = ['hot' => 0];
    public function news()
    {
        return $this->belongsToMany(News::class, NewsTags::class, 'news_id','tags_id');
    }
}

中间表模型NewsTags

<?php
namespace app\admin\model;
use think\model\Pivot;

class NewsTags extends Pivot
{

}
  1. 由于使用的路由实现类似第一页/tag/ThinkPHP/1.html到第二页/tag/ThinkPHP/2.html这种分页,需要修改官方分页类,代码如下:
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------

namespace app\common;

use think\Paginator;

/**
 * Bootstrap 分页驱动
 */
class Bootstrap extends Paginator
{
    /**
     * 获取页码对应的链接(重写父类的url方法)
     *
     * @access protected
     * @param int $page
     * @return string
     */
    protected function url(int $page): string
    {
        if ($page <= 0) {
            $page = 1;
        }

        if (strpos($this->options['path'], '[PAGE]') === false) {
//            $parameters = [$this->options['var_page'] => $page];
            $path       = $this->options['path'];
        } else {
//            $parameters = [];
            $path       = str_replace('[PAGE]', $page, $this->options['path']);
        }

        $url = $path;
        $url=substr($url,0,strrpos($url,'/'));//  /framework/1.html  截取 /framework
        $url=$url.'/'.$page.'.html';
        $parameters = $this->options['query'];
        if (!empty($parameters)) {
            $url .= '?' . http_build_query($parameters, '', '&');
        }
        return $url . $this->buildFragment();
    }

    /**
     * 上一页按钮
     * @param string $text
     * @return string
     */
    protected function getPreviousButton(string $text = "上一页"): string
    {

        if ($this->currentPage() <= 1) {
            return $this->getDisabledTextWrapper($text);
        }

        $url = $this->url(
            $this->currentPage() - 1
        );

        return $this->getPageLinkWrapper($url, $text);
    }

    /**
     * 下一页按钮
     * @param string $text
     * @return string
     */
    protected function getNextButton(string $text = '下一页'): string
    {
        if (!$this->hasMore) {
            return $this->getDisabledTextWrapper($text);
        }

        $url = $this->url($this->currentPage() + 1);

        return $this->getPageLinkWrapper($url, $text);
    }

    /**
     * 页码按钮
     * @return string
     */
    protected function getLinks(): string
    {
        if ($this->simple) {
            return '';
        }

        $block = [
            'first'  => null,
            'slider' => null,
            'last'   => null,
        ];

        $side   = 3;
        $window = $side * 2;

        if ($this->lastPage < $window + 6) {
            $block['first'] = $this->getUrlRange(1, $this->lastPage);
        } elseif ($this->currentPage <= $window) {
            $block['first'] = $this->getUrlRange(1, $window + 2);
            $block['last']  = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
        } elseif ($this->currentPage > ($this->lastPage - $window)) {
            $block['first'] = $this->getUrlRange(1, 2);
            $block['last']  = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage);
        } else {
            $block['first']  = $this->getUrlRange(1, 2);
            $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side);
            $block['last']   = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
        }

        $html = '';

        if (is_array($block['first'])) {
            $html .= $this->getUrlLinks($block['first']);
        }

        if (is_array($block['slider'])) {
            $html .= $this->getDots();
            $html .= $this->getUrlLinks($block['slider']);
        }

        if (is_array($block['last'])) {
            $html .= $this->getDots();
            $html .= $this->getUrlLinks($block['last']);
        }

        return $html;
    }

    /**
     * 渲染分页html
     * @return mixed
     */
    public function render()
    {
        if ($this->hasPages()) {
            if ($this->simple) {
                return sprintf(
                    '<ul class="pager">%s %s</ul>',
                    $this->getPreviousButton(),
                    $this->getNextButton()
                );
            } else {
                return sprintf(
                    '<ul class="pagination">%s %s %s</ul>',
                    $this->getPreviousButton(),
                    $this->getLinks(),
                    $this->getNextButton()
                );
            }
        }
    }

    /**
     * 生成一个可点击的按钮
     *
     * @param  string $url
     * @param  string $page
     * @return string
     */
    protected function getAvailablePageWrapper(string $url, string $page): string
    {
        return '<li><a href="' . htmlentities($url) . '">' . $page . '</a></li>';
    }

    /**
     * 生成一个禁用的按钮
     *
     * @param  string $text
     * @return string
     */
    protected function getDisabledTextWrapper(string $text): string
    {
        return '<li class="disabled"><span>' . $text . '</span></li>';
    }

    /**
     * 生成一个激活的按钮
     *
     * @param  string $text
     * @return string
     */
    protected function getActivePageWrapper(string $text): string
    {
        return '<li class="active"><span>' . $text . '</span></li>';
    }

    /**
     * 生成省略号按钮
     *
     * @return string
     */
    protected function getDots(): string
    {
        return $this->getDisabledTextWrapper('...');
    }

    /**
     * 批量生成页码按钮.
     *
     * @param  array $urls
     * @return string
     */
    protected function getUrlLinks(array $urls): string
    {
        $html = '';

        foreach ($urls as $page => $url) {
            $html .= $this->getPageLinkWrapper($url, $page);
        }

        return $html;
    }

    /**
     * 生成普通页码按钮
     *
     * @param  string $url
     * @param  string    $page
     * @return string
     */
    protected function getPageLinkWrapper(string $url, string $page): string
    {
        if ($this->currentPage() == $page) {
            return $this->getActivePageWrapper($page);
        }

        return $this->getAvailablePageWrapper($url, $page);
    }
}

  1. 页面渲染数据
<!--lbox begin-->
        <div class="lbox">

            <div class="whitebg bloglist">
                <h2 class="htitle"><span class="con_nav">您现在的位置是:<a href='index.html'>首页</a>&nbsp;>&nbsp;TAG信息列表&nbsp;>&nbsp;程序员</span>程序员</h2>
                <ul>
                    <volist name="tag.news" id="vo">
                    <li>
                        <h3 class="blogtitle"><a href="{{:url('article.index',['id'=>$vo.id])}}" target="_blank">{{$vo.title}}</a></h3>
                        <span class="blogpic imgscale">
                            <i><a href="index10.html" target="_blank">网站公告</a></i>
                            <a href="{{:url('article.index',['id'=>$vo.id])}}" title="{{$vo.title}}"><img src="{{$vo.cover}}" alt="{{$vo.title}}"></a></span>
                        <p class="blogtext">{{$vo.abstract}}</p>
                        <p class="bloginfo">
                            <i class="avatar"><img src="/static/picture/avatar.jpg"></i>
                            <span></span><span></span><span>2018-11-08</span>
                            <span>【<a href="{{:url($vo.newsCategory.route_name,['page'=>1])}}" target="_blank">{{$vo.newsCategory.title}}</a>】</span></p>
                        <a href="27.html" class="viewmore">阅读更多</a> </li>
                    </volist>
                </ul>
                <!--pagelist-->
                {{$tag.news|raw}}
                <!--pagelist end-->
            </div>
            <!--bloglist end-->
        </div>

实现如下分页结果。


第一页
第二页
  1. 代码比较繁琐,不使用模型直接使用db关联查询分页更快,或者直接使用中间表模型关联,使用中间表一对多关联文章,使用hasWhere过滤筛选数据,本人测试过使用hasWhere多对多关联数据,但提示不支持改关联类型。
    又更优的方式提出,欢迎大家一起交流学习。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容