😆Laravel pagination pretty URL for SEO (ok)

https://stackoverflow.com/questions/20974404/laravel-pagination-pretty-url

C:\xampp82\htdocs\phongkhamvn\database\seeders\DatabaseSeeder.php

<?php
namespace Database\Seeders;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;
class DatabaseSeeder extends Seeder
{
  /**
   * Seed the application's database.
   */
  public function run(): void
  {
    User::factory(10)->create();
    $this->call(TaskSeeder::class);
  }
}

C:\xampp82\htdocs\phongkhamvn\routes\web.php

<?php
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
  return view('welcome');
});
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('tasks/page/{page}', [TaskController::class, 'index'])->name('tasks.index.page');
Route::resource('tasks', TaskController::class);

C:\xampp82\htdocs\phongkhamvn\app\Models\Traits\PaginateTrait.php

<?php
namespace App\Models\Traits;
use Illuminate\Pagination\Paginator;
trait PaginateTrait
{
  public static function scopePaginateUri($query, $items, $page)
  {
    $action = app('request')->route()->getActionName();
    $parameters = app('request')->route()->parameters();
    $parameters['page'] = '##'; // ## == %23%23
    $current_url = action($action, $parameters);
    $current_url = preg_replace('/[\?\=]/', '/', $current_url);
    Paginator::currentPageResolver(function () use ($page) {
      return $page;
    });
    $paginate = $query->paginate($items);
    $links = preg_replace('@href="(.*/?page=(\d+))"@U', 'href="' . str_replace('%23%23', '$2', $current_url) . '"', $paginate->render());
    $paginate->linksUri = $links;
    return $paginate;
  }
}

C:\xampp82\htdocs\phongkhamvn\app\Http\Controllers\TaskController.php

<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
  /**
   * Display a listing of the resource.
   */
  public function index($page = 1)
  {
    // $tasks = Task::all();
    $tasks = Task::paginateUri(10, $page);
    return view('tasks.index', compact('tasks'));
  }
  /**
   * Show the form for creating a new resource.
   */
  public function create()
  {
    //
  }
  /**
   * Store a newly created resource in storage.
   */
  public function store(Request $request)
  {
    //
  }
  /**
   * Display the specified resource.
   */
  public function show(string $id)
  {
    //
  }
  /**
   * Show the form for editing the specified resource.
   */
  public function edit(string $id)
  {
    //
  }
  /**
   * Update the specified resource in storage.
   */
  public function update(Request $request, string $id)
  {
    //
  }
  /**
   * Remove the specified resource from storage.
   */
  public function destroy(string $id)
  {
    //
  }
}

C:\xampp82\htdocs\phongkhamvn\resources\views\tasks\index.blade.php

@extends('layouts.app')
@section('content')
<div class="container">
  <div class="row justify-content-center">
    <div class="col-md-12">
      <h2>Listagem de Tarefas <a href="{{ route('tasks.create') }}" class="btn btn-sm btn-primary">Novo</a></h2>
      <table class="table table-hover">
        <thead>
          <tr>
            <th scope="col">#</th>
            <th scope="col">Name</th>
            <th scope="col">Status</th>
            <th scope="col">Ações</th>
          </tr>
        </thead>
        <tbody>
          @foreach($tasks as $task)
          <tr>
            <th scope="row">{{ $task->id }}</th>
            <td>{{ $task->name }}</td>
            <td>{{ $task->status ? 'Concluída' : 'Aberta' }}</td>
            <td>
              <a href="{{ route('tasks.show', ['task' => $task->id]) }}" class="btn btn-sm btn-primary">Ver</a>
              <a href="{{ route('tasks.edit', ['task' => $task->id]) }}" class="btn btn-sm btn-primary">
                Editar
              </a>
              <form style="display: inline;" action="{{ route('tasks.destroy', ['task' => $task->id]) }}" method="POST">
                @csrf
                @method('DELETE')
                <button type="submit" class="btn btn-sm btn-danger">deletar</button>
              </form>
            </td>
          </tr>
          @endforeach
        </tbody>
      </table>
    </div>
    <div class="col-md-12">
      {!! $tasks->linksUri !!}
    </div>
  </div>
</div>
@endsection

C:\xampp82\htdocs\phongkhamvn\app\Providers\AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;

class AppServiceProvider extends ServiceProvider
{
  /**
   * Register any application services.
   */
  public function register(): void
  {
    //
  }
  /**
   * Bootstrap any application services.
   */
  public function boot(): void
  {
    Paginator::useBootstrap();
  }
}

C:\xampp82\htdocs\phongkhamvn\resources\views\layouts\app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- CSRF Token -->
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>{{ config('app.name', 'Laravel') }}</title>
  <!-- Fonts -->
  <link rel="dns-prefetch" href="//fonts.gstatic.com">
  <!-- Scripts -->
  @vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>
<body>
  <div id="app">
    <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
      <div class="container">
        <a class="navbar-brand" href="{{ url('/') }}">
          {{ config('app.name', 'Laravel') }}
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <!-- Left Side Of Navbar -->
          <ul class="navbar-nav me-auto">
          </ul>
          <!-- Right Side Of Navbar -->
          <ul class="navbar-nav ms-auto">
            <!-- Authentication Links -->
            @guest
            @if (Route::has('login'))
            <li class="nav-item">
              <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
            </li>
            @endif
            @if (Route::has('register'))
            <li class="nav-item">
              <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
            </li>
            @endif
            @else
            <li class="nav-item dropdown">
              <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
                aria-haspopup="true" aria-expanded="false" v-pre>
                {{ Auth::user()->name }}
              </a>
              <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                  {{ __('Logout') }}
                </a>
                <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                  @csrf
                </form>
              </div>
            </li>
            @endguest
          </ul>
        </div>
      </div>
    </nav>
    <main class="py-4">
      @yield('content')
    </main>
  </div>
</body>
</html>

14

Here's a hacky workaround. I am using Laravel v4.1.23. It assumes page number is the last bit of your url. Haven't tested it deeply so I'm interested in any bugs people can find. I'm even more interested in a better solution :-)

Route:

Route::get('/articles/page/{page_number?}', function($page_number=1){
    $per_page = 1;
    Articles::resolveConnection()->getPaginator()->setCurrentPage($page_number);
    $articles = Articles::orderBy('created_at', 'desc')->paginate($per_page);
    return View::make('pages/articles')->with('articles', $articles);
});

View:

<?php
    $links = $articles->links();
    $patterns = array();
    $patterns[] = '/'.$articles->getCurrentPage().'\?page=/';
    $replacements = array();
    $replacements[] = '';
    echo preg_replace($patterns, $replacements, $links);
?>

Model:

<?php
class Articles extends Eloquent {
    protected $table = 'articles';
}

Migration:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration {

    public function up()
    {
        Schema::create('articles', function($table){
            $table->increments('id');
            $table->string('slug');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('articles');
    }
}

Last updated