Laravel 中通过自定义分页器分页方法实现伪静态分页链接以利于 SEO

我们知道,Laravel 自带的分页器方法包含 simplePaginate 和 paginate 方法,一个返回不带页码的分页链接,另一个返回带页码的分页链接,但是这两种分页链接页码都是以带问号的动态参数形式附加在查询字符串中,形如 https://laravelacademy.org?page=100,但是这种包含动态参数的 URL 格式对 SEO 不友好,我们最好将其转化为 https://laravelacademy.org/page/100 这种不带问号的伪静态分页链接格式,遗憾的是 Laravel 并没有提供对这种伪静态分页链接格式的自定义支持,只好自己动手了。

我们将基于 paginate 方法实现的分页进行扩展,只是修改其分页链接格式,该方法底层调用了 Illuminate\Pagination\LengthAwarePaginator 类实现分页,所以我们需要自定义一个继承自该分页器类的 app/Utils/AcademyPaginator.php,初始化代码如下:

<?php

namespace App\Utils;

use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator as BasePaginator;

class AcademyPaginator extends BasePaginator
{
}

在 Laravel 自带的分页器中,默认分页链接是通过 url 方法生成并返回的,该方法定义在抽象类 vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php 中:

/**
 * Get the URL for a given page number.
 *
 * @param  int  $page
 * @return string
 */
public function url($page)
{
    if ($page <= 0) {
        $page = 1;
    }

    // If we have any extra query string key / value pairs that need to be added
    // onto the URL, we will put them in query string form and then attach it
    // to the URL. This allows for extra information like sortings storage.
    $parameters = [$this->pageName => $page];

    if (count($this->query) > 0) {
        $parameters = array_merge($this->query, $parameters);
    }

    return $this->path
                    .(Str::contains($this->path, ‘?‘) ? ‘&‘ : ‘?‘)
                    .http_build_query($parameters, ‘‘, ‘&‘)
                    .$this->buildFragment();
}

它是在这里将分页信息作为查询参数的一部分放到分页链接中,所以我们需要在自定义的 AcademyPaginator 类中重写这个方法覆盖父类实现:

/**
 * 重写页面 URL 实现代码,去掉分页中的问号,实现伪静态链接
 * @param int $page
 * @return string
 */
public function url($page)
{
    if ($page <= 0) {
        $page = 1;
    }

    // 移除路径尾部的/
    $path = rtrim($this->path, ‘/‘);

    // 如果路径中包含分页信息则正则替换页码,否则将页码信息追加到路径末尾
    if (preg_match(‘/\/page\/\d+/‘, $path)) {
        $path = preg_replace(‘/\/page\/\d+/‘, ‘/page/‘ . $page, $path);
    } else {
        $path .= ‘/page/‘ . $page;
    }
    $this->path = $path;

    if ($this->query) {
        $url = $this->path . (Str::contains($this->path, ‘?‘) ? ‘&‘ : ‘?‘)
            . http_build_query($this->query, ‘‘, ‘&‘)
            . $this->buildFragment();
    } elseif ($this->fragment) {
        $url = $this->path . $this->buildFragment();
    } else {
        $url = $this->path;
    }

    return $url;
}

实现原理很简单,就是判断页面路径中是否已经包含类似 /page/100 这种页码信息,如果不包含,则将其添加到路径中,否则对页码值进行正则替换,最后把新的路径结合之前的查询参数和锚点信息拼接为新的完整分页链接返回给调用方。

除此之外,因为分页链接格式变了,我们还要在 AcademyPaginator 对父类设置当前页码的方法进行重写,还是一个对当前分页链接的正则匹配:

/**
 * 重写当前页设置方法
 *
 * @param  int  $currentPage
 * @param  string  $pageName
 * @return int
 */
protected function setCurrentPage($currentPage, $pageName)
{
    if (!$currentPage && preg_match(‘/\/page\/(\d+)/‘, $this->path, $matches)) {
        $currentPage = $matches[1];
    }

    return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1;
}

至此,我们自定义的分页器类已经编写好了,接下来还要将这个分页器注册到模型类的查询构建器中以便可以像simplePaginate 和 paginate 方法那样在模型实例上调用,这里我们需要借助查询构建器 Illuminate\Database\Eloquent\Builder 提供的静态 macro 方法在运行时动态扩展其提供的方法,我们还是在 AcademyPaginator 中定义这个方法扩展实现:

/**
 * 将新增的分页方法注册到查询构建器中,以便在模型实例上使用
 * 注册方式:
 * 在 AppServiceProvider 的 boot 方法中注册:AcademyPaginator::rejectIntoBuilder();
 * 使用方式:
 * 将之前代码中在模型实例上调用 paginate 方法改为调用 seoPaginate 方法即可:
 * Article::where(‘status‘, 1)->seoPaginate(15, [‘*‘], ‘page‘, page);
 */
public static function injectIntoBuilder()
{
    Builder::macro(‘seoPaginate‘, function ($perPage, $columns, $pageName, $page) {
        $perPage = $perPage ?: $this->model->getPerPage();

        $items = ($total = $this->toBase()->getCountForPagination())
            ? $this->forPage($page, $perPage)->get($columns)
            : $this->model->newCollection();

        $options = [
            ‘path‘ => Paginator::resolveCurrentPath(),
            ‘pageName‘ => $pageName,
        ];

        return Container::getInstance()->makeWith(AcademyPaginator::class, compact(
            ‘items‘, ‘total‘, ‘perPage‘, ‘page‘, ‘options‘
        ));
    });
}

这样,在模型实例上调用 seoPaginate 方法,将通过 AcademyPaginator 进行分页,最后我们在 AppServiceProvider 的 boot 方法中全局调用这个注入:

// 为查询构建器注入自己实现的分页器方法
AcademyPaginator::injectIntoBuilder();

接下来,就可以在自己的代码中编写以下这种代码实现伪静态分页链接了:

$articles = Article::with(‘author‘, ‘category‘)->public()->orderBy(‘id‘, ‘desc‘)->seoPaginate($pageSize, $this->listColumns, ‘page‘, $page);

原文地址:https://www.cnblogs.com/sgm4231/p/10283614.html

时间: 2024-08-28 13:49:22

Laravel 中通过自定义分页器分页方法实现伪静态分页链接以利于 SEO的相关文章

Android 中使用自定义字体的方法

1.Android系统默认支持三种字体,分别为:“sans”, “serif”, “monospace 2.在Android中可以引入其他字体 . <?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:Android="http://schemas.android.com/apk/res/android" Android:layout_width="fill

JavaScript中创建自定义对象的方法

本文内容参考JavaScript高级程序设计(第3版)第6章:面向对象的程序设计 ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”我所理解的就是对象就是一个结构体,结构体中有一些它的基本属性以及对结构体处理的方法,把它们封装起来称为一个整体.JS中所有的对象都是基于一个引用类型创建,这个引用类型可以是原生类型,如Array,Date等,也可以是开发人员自定义的类型. 下面主要总结下JS中创建对象的几种模式,分析他们各自的优缺点. 1. 工厂模式 /****

Javascript 中创建自定义对象的方法(设计模式)

Javascript 中创建对象,可以有很多种方法. Object构造函数/对象字面量: 抛开设计模式不谈,使用最基本的方法,就是先调用Object构造函数创建一个对象,然后给对象添加属性. 1 var student = new Object(); 2 student.name = "xiao ming"; 3 student.age = 20; 4 student.getName = function () { 5 alert(this.name); 6 } 熟悉javascrip

elementui中el-upload自定义上传方法中遇到的问题

由于el-upload控件中自定义的upload方法在上传文件中是以FormData的格式上传,后台服务器无法解析这种格式的body,所以通过http-request属性自定义了一个上传方法. <el-upload class="upload-demo" ref="upload" action="http://127.0.0.1:5000/json/import" :http-request="myUpload" :on

Laravel中利用队列发送邮件的方法示例

https://www.jb51.net/article/121647.htm 本文主要给大家介绍了关于Laravel中队列发送邮件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 批量处理任务的场景在我们开发中是经常使用的,比如邮件群发,消息通知,短信,秒杀等等,我们需要将这个耗时的操作放在队列中来处理,从而大幅度缩短Web请求和相应的时间.下面讲解下Laravel中队列的使用 1.配置文件 config/queue.php ? 1 2 3 4 5 6 7 8 9 1

iOS开发小技巧--获取自定义的BarButtonItem中的自定义View的方法(customView)

如果BarButtonItem是通过[[UIBarButtonItem alloc] initWithCustomView:(nonnull UIView *)]方法设置的.某些情况下需要修改BarButtonItem中自定义View的某些属性,例如显示的文字或者显示的图片. 可以通过BarButtonItem的customView获取自定义的View.

cocos2d-js中的自定义监听方法(一个有趣的示例)

app.js中的实现: 1 var HelloWorldLayer = cc.Layer.extend({ 2 3 defualt:null, 4 5 ctor:function () { 6 7 this._super(); 8 mainscene = ccs.load(res.MainScene_json).node; 9 this.addChild(mainscene); 10 11 var sprite1 = ccui.helper.seekWidgetByName(mainscene,

Thinkphp模板中使用自定义函数的方法

注意:自定义函数要放在项目应用目录/common/common.php中. 这里是关键. 模板变量的函数调用格式:{$varname|function1|function2=arg1,arg2,### } 说明: { 和 $ 符号之间不能有空格,后面参数的空格就没有问题: ###表示模板变量本身的参数位置 : 支持多个函数,函数之间支持空格 : 支持函数屏蔽功能,在配置文件中可以配置禁止使用的函数列表 : 支持变量缓存功能,重复变量字串不多次解析. 使用例子: {$webTitle|md5|st

laravel中delete()方法和destroy()方法的区别

delete()方法是实例方法,需要查询到相应的数据并通过模型实例调用 destroy()方法可以直接调用,通过索引删除记录 举个栗子: 1 /*delete()方法删除*/ 2 //先查找记录 3 $blog = Blog::find(1); 4 if($blog){ 5 //再删除记录 6 if($blog->delete()){ 7 echo "删除成功!"; 8 }else{ 9 echo '删除失败!'; 10 } 11 }else{ 12 echo "文章不