Может войдёшь?
Черновики Написать статью Профиль

Свой постраничный вывод с плюшками

laravel php paginator

Еще во времена laravel 3 поступило мне задание от очередного оптимизатора, которое заключалось в следующем:

  1. Постраничный вывод должен генерировать ссылки вида /news/page2, /news/page3 и тд
  2. Страницы /news/page1 не должно быть вообще, должно быть просто /news
  3. В title нужно добавить номер страницы как-то так: «оригинальный тайтл» — страница «номер страницы»
  4. Автоматическая генерация link rel="canonical" для страниц с номером, добавление link rel="prev" и link rel="next"

Посмотрев исходники я понял что проще написать свой FPagination. F — потому что fucking friendly.

Надежный как автомат Калашникова и простой как сатиновые трусы.

PHP
<?php

namespace App\Classes;

class 
FPagination {

  public function 
__construct($items, $currentPage, $lastPage, $perPage = '20', $pageName = 'page', $url)
  {
    
$this->items = $items;
    
$this->currentPage = $currentPage;
    
$this->lastPage = $lastPage;
    
$this->pageName = $pageName;
    
$this->url = $url;
  }


  public function 
links()
  {
    if(
$this->lastPage > 1)
    {
      if(
$this->lastPage < 13)
      {
        
$content = $this->getPageRange(1, $this->lastPage);
      }
      else
      {
        
$content = $this->getPageSlider();
      }

      
$pagination = '<nav class="pagination">'
                    
.$this->getPreviousTouch()
                    .
$this->getNextTouch()
                    .
'<ul class="pagination-list is-hidden-mobile">'
                    
.$this->getPrevious()
                    .
$content
                    
.$this->getNext()
                    .
'</ul></nav>';
    }
    else
    {
      
$pagination = '';
    }

    return 
$pagination;
  }


  public function 
getPageRange($start, $end)
  {
    
$pages = '';

    for(
$page = $start; $page <= $end; $page++)
    {
      if(
$this->currentPage == $page)
      {
        
$pages .= $this->getActivePageWrapper($page);
      }
      else if(
$page == 1)
      {
        
$pages .= $this->getBase($page);
      }
      else
      {
        
$pages .= $this->getLink($page);
      }
    }

    return 
$pages;
  }


  public function 
getActivePageWrapper($text)
  {
    return 
'<span class="pagination-link is-current">'.$text.'</span>';
  }


  public function 
getBase($page)
  {
    
$link = '<a class="pagination-link" href="'.$this->url.'">'.$page.'</a>';

    return 
$this->getPageLinkWrapper($link);
  }


  public function 
getPageLinkWrapper($link)
  {
    return 
'<li>'.$link.'</li>';
  }


  public function 
getLink($page)
  {
    
$link = '<a class="pagination-link" href="'.$this->url.'/'.$this->pageName.$page.'">'.$page.'</a>';

    return 
$this->getPageLinkWrapper($link);
  }


  protected function 
getPageSlider()
  {
    
$window = 6;

    if(
$this->currentPage <= $window)
    {
      
$content = $this->getPageRange(1, $window + 2).$this->getFinish();
    }
    else if(
$this->currentPage >= $this->lastPage - $window)
    {
      
$start = $this->lastPage - 8;

      
$content = $this->getStart().$this->getPageRange($start, $this->lastPage);
    }
    else
    {
      
$content = $this->getStart().$this->getAdjacentRange().$this->getFinish();
    }

    return 
$content;
  }


  public function 
getFinish()
  {
    return 
$this->getDots().$this->getPageRange($this->lastPage - 1, $this->lastPage);
  }


  public function 
getDots()
  {
    return 
'<li class="pagination-ellipsis">...</li>';
  }


  public function 
getStart()
  {
    return 
$this->getPageRange(1, 2).$this->getDots();
  }


  public function 
getAdjacentRange()
  {
    return 
$this->getPageRange($this->currentPage - 3, $this->currentPage + 3);
  }


  public function 
getPreviousTouch($text = '« Предыдущая')
  {
    if(
$this->currentPage <= 1)
    {
      
$link = '<span class="pagination-previous is-hidden-tablet" disabled="disabled">'.$text.'</span>';
    }
    else
    {
      
$link = '<a class="pagination-previous is-hidden-tablet" href="'.$this->prevUrl().'">'.$text.'</a>';
    }

    return 
$link;
  }


  public function 
prevUrl()
  {
    if(
$this->currentPage <= 2)
    {
      
$url = $this->url;
    }
    else
    {
      
$url = $this->url.'/'.$this->pageName.($this->currentPage - 1);
    }

    return 
$url;
  }


  public function 
getNextTouch($text = 'Следующая »')
  {
    if(
$this->currentPage >= $this->lastPage)
    {
      
$link = '<span class="pagination-next is-hidden-tablet" disabled="disabled">'.$text.'</span>';
    }
    else
    {
      
$link = '<a class="pagination-next is-hidden-tablet" href="'.$this->nextUrl().'">'.$text.'</a>';
    }

    return 
$link;
  }


  public function 
nextUrl()
  {
    return 
$this->url.'/'.$this->pageName.($this->currentPage + 1);
  }


  public function 
getPrevious($text = '«')
  {
    if(
$this->currentPage <= 1)
    {
      return 
$this->getDisabledTextWrapper($text);
    }
    else
    {
      
$link = '<a class="pagination-link" href="'.$this->prevUrl().'">'.$text.'</a>';
    }

    return 
$this->getPageLinkWrapper($link);
  }


  public function 
getDisabledTextWrapper($text)
  {
    return 
'<li class="pagination-link" disabled="disabled">'.$text.'</li>';
  }


  public function 
getNext($text = '»')
  {
    if(
$this->currentPage >= $this->lastPage)
    {
      return 
$this->getDisabledTextWrapper($text);
    }

    
$link = '<a class="pagination-link" href="'.$this->nextUrl().'">'.$text.'</a>';

    return 
$this->getPageLinkWrapper($link);
  }


  public function 
getTitleAppend($text)
  {
    if(
$this->currentPage > 1)
    {
      
$title = $text.' - страница '.$this->currentPage;
    }
    else
    {
      
$title = $text;
    }

    return 
$title;
  }


  public function 
getSeoBlock()
  {
    
$canonical = '<link rel="canonical" href="'.$this->url.'">';

    
$prev = '<link rel="prev" href="'.$this->prevUrl().'">';
    
$next = '<link rel="next" href="'.$this->nextUrl().'">';

    if(
$this->currentPage <= 1)
    {
      
$content = $next;
    }
    else if(
$this->currentPage > 1 && $this->currentPage < $this->lastPage)
    {
      
$content = $canonical.$prev.$next;
    }
    else
    {
      
$content = $canonical.$prev;
    }

    return 
$content;
  }
}

Данная реализация заточена под Bulma framework но поменять классы на верстку Bootstrap или любую другую не сложно.

Как этим чудом пользоваться в Laravel?

Я делаю так:

Создаю функцию помощника, даже две

PHP
//resources/helpers.php
<?php

// эта функция приводит $currentPage к нужному для FPagination виду. Можно и без нее, если не нужна дополнительная логика, или если она реализована иначе в другом месте. Например через Middleware
function currentPage($currentPage = null)
{
    
$pageName = 'page';

    
$oldPage = $currentPage;

    if(
is_null($currentPage))
    {
        
$currentPage = 1;
    }
    else
    {
        
$currentPage = str_replace($pageName, '', $currentPage);
        
$currentPage = (int)preg_replace("/[^0-9]/", '', $currentPage);

        
$newPage = $pageName.$currentPage;

        if(
$newPage != $oldPage or $newPage == 'page1')
        {
            
abort(404);
        }
    }

    return 
$currentPage;
}

// эта функция формирует параметры для передачи в FPagination и возвращает результат. Да, комментатор из меня тот еще
function FPagination($sql, $currentPage, $perPage = 20, $action = null)
{
    
$pageName = 'page';

    if(
is_null($action))
    {
        
$action = str_replace('App\Http\Controllers\\', '', \Route::currentRouteAction());
    }

    
$currentPage = currentPage($currentPage);

    
$total = $sql->count();

    
$lastPage = ceil($total / $perPage);

    if(
$currentPage > $lastPage)
    {
        
abort(404);
    }
    else
    {
        
$items = $sql->skip(($currentPage * $perPage) - $perPage)
                     ->
take($perPage)
                     ->
get();

        
$routeParameters = array_values(\Route::current()
                                              ->
parameters());

        
$count = count($routeParameters);

        if(
$count >= 1)
        {
            if(
starts_with($routeParameters[$count - 1], $pageName))
            {
                unset(
$routeParameters[$count - 1]);
            }
        }

        
$url = action($action, $routeParameters);

        return new \
App\Classes\FPagination($items, $currentPage, $lastPage, $perPage, $pageName, $url);
    }
}

Еще нужно добавить scope в модель. Можно сделать глобальный, но я предпочитаю локальные, на случай дополнительных доработок. Можно и без него, это скорее для красоты.

PHP
<?php

namespace App\Models;

use 
Illuminate\Database\Eloquent\Model;

class 
News extends Model {

  public function 
scopePagination($query, $currentPage, $perPage = 20, $action = null)
  {
    return 
FPagination($query, $currentPage, $perPage, $action);
  }
}

В контроллере это выглядит так:

PHP
<?php

namespace App\Http\Controllers;

class 
NewsController extends Controller {

    public function 
getIndex($currentPage = null)
    {
        
$view = view('news/index');

        
$view->news = \App\Models\News::pagination($currentPage, 30);

        return 
$view;
    }
}

Если метод контроллера принимает больше одного параметра, $currentPage = null должен быть последним.

В представлении 'news/index'

PHP
@extends('base/page')

@
section('meta')
    {{ 
$users->getSeoBlock() }}
@
endsection

@section('title')
    {{ 
$users->getTitleAppend('Новости') }}
@
endsection

@section('content')

    @foreach(
$news->items as $item)

    @endforeach

    {{ 
$news->links() }}
@
endsection

Данный класс удобно тягать с собой из проекта в проект, довольно просто настраивается под любую верстку, если почесать репу можно прикрутить и к другим фреймворкам или формировать им постраничный вывод для массивов\коллекций. Composter Composer пакет собирать лениво, мне проще так — как в старое доброе время.

Навеяно этим.

Как вы считаете, полезен ли этот материал? Да Нет

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.