Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 16.04.2017 23:03:02

SEOFriendlyPaginator - без зеркала ?page=1 и несуществующих страниц

Сделал для себя решение для того, чтобы убрать из пагинатора зеркало страницы /?page=1 (оно зеркалит /) и убрал вывод пустой страницы при запросе аля /?page=99999.
Выкладываю, чтобы вы покритиковали и сказали как можно было сделать проще и изящнее. В итоге буду не против, если кто-то выложит это в виде статьи, если я не сделаю этого в течении месяца после создания данной темы.

Изначально хотел просто подменить view (resources/views/vendor/pagination/default.blade.php), но уткнулся в то, что в LengthAwarePaginator нет метода для получения предыдущей страницы, есть только метод для получения url предыдущей страницы ($paginator->previousPageUrl()). Если пробовать разбирать через него, то это будет плохим способом, т.к. изначально пагинатор может выводиться на разных страницах, соответственно переписывать все пути - не кошерно.

Потом наткнулся на комментарий @FreeWebber и понял, что это можно сделать через наследования, придется только чуть-чуть поколдовать.

Пронаследовал Illuminate\Pagination\LengthAwarePaginator и изменил метод url() - сделал, чтобы при визите на любую страницу с ?page=X, где X > 1 - у первой страницы (page=1) не добавлялся GET-параметр "page=1":

<?php

namespace App\Utils;

use Illuminate\Support\Str;
use Illuminate\Pagination\LengthAwarePaginator;


class SEOFriendlyPaginator extends LengthAwarePaginator {
    
    public function url($page)
    {
        if ($page <= 0) {
            $page = 1;
        }
        
        $parameters = array();
        if ($page > 1) {
            $parameters = [$this->pageName => $page];
        }

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

        $url = $this->path;
        
        $params_delimiter = (Str::contains($this->path, '?') ? '&' : '?');
        $params_query = http_build_query($parameters, '', '&');
        $fragment = $this->buildFragment();
        
        if (!empty($params_query)) {
            $url .= $params_delimiter . $params_query;
        }
        if (!empty($fragment)) {
            $url .= $fragment;
        }
        
        return $url;
    }
}

Затем в своем Controller использовал этот пагинатор и:
1. Сделал редирект с /?page=1 на /
2. Сделал вывод 404 ошибки, если каким-то образом у нас хотят получить страницу, которой не существует (аля /?page=99999)

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Article;

use App\Utils\SEOFriendlyPaginator;

class SectionController extends Controller
{
    public function home(Request $request) {
        $per_page = config('project.per_page');  // добавил соответствующий конфиг
        $page = $request->input('page');
        $items = Article::filtered();  // здесь применяется мой собственный scope - filtered, это что-то вроде published, но с нужной мне сортировкой
        $total = $items->count();
        if (!is_null($page)) {
            if ($page == 1) {
                return redirect(route('home'), 301); // здесь я делаю редирект на роут 'home', не придумал как сделать редирект через $request
            }
            if ($page > ceil($total / $per_page)) {
                abort(404);
            }
        } else {
            $page = 1;
        }
        
        $items = $items->skip(($page - 1) * $per_page)->take($per_page)->get(); // берем только нужные элементы
        $items = new SEOFriendlyPaginator($items, $total, $per_page, $page);
        
        return view('sections.index', [
            'items' => $items
        ]);
    }
}

И в дальнейшем во view я теперь делаю:

@foreach ($items as $item)
    {{-- вывод каждого элемента --}}
@endforeach


{{-- Вывод пагинатора --}}
{{ $items->links() }}

Всё. Как уже и писал выше - предложения, критика и советы - приветствуются! smile

Изменено Lord_Alfred (16.04.2017 23:09:33)

Не в сети

#2 16.04.2017 23:17:22

Re: SEOFriendlyPaginator - без зеркала ?page=1 и несуществующих страниц

Хм. Столкнулся с тем, что мой пагинатор корректно работает только для /. В категориях он уже не знает о правильном пути, что странно (в пагинации всегда /?page=X, а не /category?page=X, как подразумевается).

Я думал, что в нём $this->path уже подразумевает "разруливание" таких ситуаций. Пойду думать как проще всего это обойти без костылей, если не напишу решения - буду благодарен за совет.

-----------

UPD:
Инициализацию пагинатора в контроллере следует сменить с:

new SEOFriendlyPaginator($items, $total, $per_page, $page);

на

new SEOFriendlyPaginator($items, $total, $per_page, $page, ['path' => $request->getPathInfo()]);

-----------

UPD2:

Тогда и редирект можно сменить
с

return redirect(route('home'), 301);

на

return redirect($request->getPathInfo(), 301);

Изменено Lord_Alfred (16.04.2017 23:25:25)

Не в сети

#3 17.04.2017 00:00:52

Re: SEOFriendlyPaginator - без зеркала ?page=1 и несуществующих страниц

UPD3:
Чтобы предотвратить зеркало /?page=0 и убрать возможность заходить на страницы с page < 0, нужно в контроллере изменить

if ($page > ceil($total / $per_page)) {

На:

if (($page > ceil($total / $per_page)) || ($page < 1)) {

Не в сети

#4 20.04.2017 18:00:06

Re: SEOFriendlyPaginator - без зеркала ?page=1 и несуществующих страниц

Опубликовал в виде статьи: https://laravel.ru/posts/768
Надеюсь, что здесь приветствуется такое smile

Не в сети

Подвал раздела