我们知道,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);

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

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

    我们知道,Laravel 自带的分页器方法包含 simplePaginate 和 paginate 方法,一个返回不带页码的分页链接,另一个返回带页码的分页链接,但是这两种分页链接页码都是以带问号的动 ...

  2. 详解如何在Laravel中增加自定义全局函数

    http://www.php.cn/php-weizijiaocheng-383928.html 如何在Laravel中增加自定义全局函数?在我们的应用里经常会有一些全局都可能会用的函数,我们应该怎么 ...

  3. django中的自定义分页器

    1.什么是自定义分页器 当我们需要在前端页面展示的数据太多的时候,我们总不能将数据展示在一页上面吧!这时,我们就需要自定义一个分页器,将数据分成特定的页数进行展示,每一页展示固定条数的数据! 2.为什 ...

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

    Javascript 中创建对象,可以有很多种方法. Object构造函数/对象字面量: 抛开设计模式不谈,使用最基本的方法,就是先调用Object构造函数创建一个对象,然后给对象添加属性. var ...

  5. php在laravel中使用自定义的Common类

    众所周知,laravel是一款高度集成的开发框架,框架内置非常多的操作方法,从而保证了我们的开发效率.但是在日常中为了满足我们的个性化业务,也需要自己去编写工具类,在laravel中我们完成编写后还需 ...

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

    本文内容参考JavaScript高级程序设计(第3版)第6章:面向对象的程序设计 ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”我所理解的就是对象就是一个结构 ...

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

    https://www.jb51.net/article/121647.htm 本文主要给大家介绍了关于Laravel中队列发送邮件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的 ...

  8. delphi中使用自定义资源的方法

    如果要在delphi中使用自定义资源文件*.res文件,比如一个光标,此时可以采用下列步骤: 1,创建包含相应的资源文件,这里是创建一个包含自定义光标的res文件. 2,在主窗体的pas文件中加入编译 ...

  9. 纯JS前端分页方法(JS分页)

    1.JS分页函数:开发过程中,分页功能一般是后台提供接口,前端只要传page(当前页码)和pageSize(每页最大显示条数)及对应的其他查询条件,就可以返回所需分页显示的数据. 但是有时也需要前端本 ...

随机推荐

  1. $(function() {....}) ,(function($){...})(jQuery)

    $(function() {....}) 是 jQuery 中的经典用法,等同于 $(document).ready(function() {....}),即在页面加载完成后才执行某个函数,如果函数中 ...

  2. 使用RocketMQ实现分布式事务

    .. todo ref https://blog.csdn.net/zhejingyuan/article/details/79480128

  3. python 模块 - pymongo模块

    mongoDB 数据库: pymongo 操作: import pymongo # 连接mongo数据库 client = pymongo.MongoClient(host='localhost', ...

  4. shell编程 之 test命令

    shell编程里的测试test命令基本可以分为3种数据类型,每种都不一样.个人更倾向于理解为条件语句的写法规则,就是test加条件加判断语句. 1 数值类型 基本可以分为6个判断:-eq等于,-ne不 ...

  5. Django中间件基础笔记

    django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 在django项目的se ...

  6. [转载]Meta Learning单排小教学

    原文链接:Meta Learning单排小教学 虽然Meta Learning现在已经非常火了,但是还有很多小伙伴对于Meta Learning不是特别理解.考虑到我的这个AI游乐场将充斥着Meta ...

  7. windows命令行获取时间

    在写Windows批处理脚本时,常常需要获取系统日期.时间戳记,用作文件名.文件夹名.log等等. 本文介绍了如何获取自订的系统日期.时间戳记. 首先,在Windows中,系统日期由以下参数获得: % ...

  8. Java序列化Serializable

    1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:deserialization(反序列化)是一种将这些字节重建成一个对象的过程. 2.什么情况下需 ...

  9. Alpha 冲刺 (4/10)

    目录 摘要 团队部分 个人部分 摘要 队名:小白吃 组长博客:hjj 作业博客:冲刺4 团队部分 后敬甲 过去两天完成了哪些任务 文字描述 主页部分图标的替换 -拍照按钮的设计和测试 GitHub代码 ...

  10. zookeeper的三种安装模式

    zookeeper的安装分为三种模式:单机模式.集群模式和伪集群模式. 1.单机模式 首先,从Apache官网下载一个Zookeeper稳定版本,本次教程采用的是zookeeper-3.4.9版本. ...