简体   繁体   中英

Pagination with Eloquent-5.3 using Slim 3

Here's my code for the pagination:

Controller:

public function index($request, $response) {

    $current_page = $request->getParam('page');

    Paginator::currentPageResolver(function() use ($current_page) {
        return $current_page;
    });

    $routes = $this->route->paginate(2);

    return $this->view->render($response, 'dashboard/dashboard.twig', [
        'routes' => $routes
    ]);
}

View:

{% for route in routes %}
    {{ route.route_name }}<br>
{% endfor %}

There's no problem with that when I run the browser. It also works whenver I add page parameter in the url.

在此输入图像描述

But when I try to add the links method in the paginate object.

{% for route in routes %}
    {{ route.route_name }}<br>
{% endfor %}

{{ routes.links() }} {# <--- This one #}

It prompts me an error

Message: Call to a member function make() on null

File: C:\\xampp\\htdocs...\\vendor\\illuminate\\pagination\\LengthAwarePaginator.php

Update:

When I try to echo the links() method inside the Controller. I found this error.

$routes = $this->route->paginate(5);
echo $routes->links();
die();

Warning: call_user_func() expects parameter 1 to be a valid callback, no array or string given in C:\\xampp\\htdocs...\\vendor\\illuminate\\pagination\\AbstractPaginator.php on line 377

Then I checked the source code, it's this one.

/**
 * Get an instance of the view factory from the resolver.
 *
 * @return \Illuminate\Contracts\View\Factory
 */
public static function viewFactory()
{
    return call_user_func(static::$viewFactoryResolver);
}

Any solution for this?

This method works in 5.2, referring to this link.

Pagination with Twig and Eloquent-5.2

But unfortunely, it doesn't work anymore.

In PHP 7 you can create anonymous class for register it for use in viewVactory:

$view = $this->container->get('view');

$view->getEnvironment()->addTest(new Twig_SimpleTest('string', function($value) {
    return is_string($value);
}));

\Illuminate\Pagination\Paginator::viewFactoryResolver(function() use ($view) {
    return new class($view) {
        private $view;
        private $template;
        private $data;

        public function __construct(\Slim\Views\Twig $view)
        {
            $this->view = $view;
        }

        public function make(string $template, $data = null)
        {
            $this->template = $template;
            $this->data = $data;
            return $this;
        }

        public function render()
        {
            return $this->view->fetch($this->template, $this->data);
        }
    };
});

\Illuminate\Pagination\Paginator::currentPageResolver(function() use ($request) {
    return $request->getParam('page');
});

return $view->render($response, 'list.twig', [
    'items' => Material::paginate(10),
]);

In list.twig :

{{ items.render('pagination.twig') | raw }}

In pagination.twig :

{% if paginator.hasPages() %}
    <ul class="pagination">
        {% for element in elements %}
            {% if element is string %}
                <li class="disabled"><span>{{ element }}</span></li>
            {% endif %}

            {% if element is iterable %}
                {% for page, url in element %}
                    {% if page == paginator.currentPage() %}
                        <li class="active"><span>{{ page }}</span></li>
                    {% else %}
                        <li><a href="{{ url }}">{{ page }}</a></li>
                    {% endif %}
                {% endfor %}
            {% endif %}
        {% endfor %}
    </ul>
{% endif %}

I had to use a combination of the answers above to get something fully working. I'll post my solution here in case it helps others.

This is Wesley's template, but upgraded to work with bootstrap 4. And as a bonus, it doesn't need the middleware to set any global variables.

// pagination.twig
{% if data is not empty and data.lastPage > 1 %}

{# Pagination shows a "window" of links to the left and right of the current page #}
{% set num_visible_pages = 20 %}

<ul class="pagination">

  <li class="page-item {{ (data.currentPage == 1) ? 'disabled' : '' }}">
    <a class="page-link" href="{{ data.url(1) }}">First</a>
  </li>

  {% for page_number in 1..(data.lastPage) %}
    {% set half_visible_links = num_visible_pages / 2 | round %}
    {% set from = data.currentPage - half_visible_links %}
    {% set to = data.currentPage + half_visible_links %}

    {# if near beginning of pages, extend end to ensure num_visible_pages are shown #}
    {% if data.currentPage < half_visible_links %}
      {# we can be sloppy because the loop iteration constrains-out-of-bounds values #}
      {% set to = (half_visible_links - data.currentPage) + to %}
    {% endif %}

    {# if near end of pages, extend beginning to ensure num_visible_pages are shown #}
    {% if (data.lastPage - data.currentPage) < half_visible_links %}
      {# we can be sloppy because the loop iteration constrains-out-of-bounds values #}
      {% set from = data.lastPage - num_visible_pages %}
    {% endif %}

    {# only print pages between "from" and "to" #}
    {% if from < page_number and page_number < to %}
      <li class="page-item {{ (data.currentPage == page_number) ? 'active' : '' }}">
        <a class="page-link" href="{{ data.url(page_number) }}">{{ page_number }}</a>
      </li>
    {% endif %}
  {% endfor %}

  <li class="page-item {{ (data.currentPage == data.lastPage) ? 'disabled' : '' }}">
    <a class="page-link" href="{{ data.url(data.lastPage) }}">Last</a>
  </li>

</ul>

{% endif %}

Which you can call in your twig template with something like this, where "items" is the result of the paginator query.

{% include 'pagination.twig'
    with {data: items} only
%}

Then, you need to tell the Eloquent Paginator how to get the "page" from the url (otherwise you'll be stuck on page 1). You can do this by calling currentPageResolver() sometime before your paginator() query. For me, I just slipped it into some middleware so it's always defined.

<?php

/**
 * Tells the Eloquent Paginator how to get the current page
 */

namespace App\Middleware;

class Pagination {

    public function __invoke($request, $response, $next)
    {
        \Illuminate\Pagination\Paginator::currentPageResolver(function() use ($request) {
            return $request->getParam('page');
        });

        return $next($request, $response);
    }

}

And let Slim know about the middleware it in your app setup.

$app->add(new App\Middleware\Pagination);

You may want to add more parameters to the url, such as sorting or toggle data. You can still use $items->appends() to do so. Note that the default path is to "/". You can remove that with $items->withPath('') so links generated with ->url() will link to the current page.

As a temporary solution, it seems I can access the paginate object's methods. So I tried to find a laravel template of pagination around the internet.

Here's the solution that I came up with.

What it does is, it will only show if the array is not empty and obviously if it renders more than one page. (Depending on the number of pages you will set up in your pagination)

And the template that I got from somewhere with modified functions that suited for my needs.

{% if arrays is not empty and arrays.lastPage > 1 %}

    <div class="paginate-wrapper">

        <ul class="pagination">

            <li class="{{ (arrays.currentPage == 1) ? 'active' : '' }}">
                <a href="{{ uri.link }}{{ (uri.request_sent) ? '&' : '?' }}page=1">First</a>
            </li>

            {% for page_number in 1..(arrays.lastPage) %}

                {% set half_total_links = 7 / 2 | round %}
                {% set from = arrays.currentPage - half_total_links %}
                {% set to = arrays.currentPage + half_total_links %}

                {% if arrays.currentPage < half_total_links %}
                    {% set to = (half_total_links - arrays.currentPage) + to %}
                {% endif %}

                {% if (arrays.lastPage - arrays.currentPage) < half_total_links %}
                    {% set from = (half_total_links - (arrays.lastPage - arrays.currentPage) - 1) - to %}
                {% endif %}

                {% if from < page_number and page_number < to %}
                    <li class="{{ (arrays.currentPage == page_number) ? 'active' : '' }}">
                        <a href="{{ uri.link }}{{ (uri.request_sent) ? '&' : '?' }}page={{ page_number }}">{{ page_number }}</a>
                    </li>
                {% endif %}
            {% endfor %}

            <li class="{{ (arrays.currentPage == arrays.lastPage) ? 'active' : '' }}">
                <a href="{{ uri.link }}{{ (uri.request_sent) ? '&' : '?' }}page={{ arrays.lastPage }}">
                    Last
                </a>
            </li>
        </ul>
    </div>
{% endif %}

Here's my Middleware on Router.

namespace App\Middleware;

use Illuminate\Database\Capsule\Manager as DB;
use App\Models\Module\Module;

class RouterMiddleware extends Middleware {

    public function __invoke($request, $response, $next) {

        $uri          = $request->getUri();
        $current_path = $uri->getPath();
        $route        = $request->getAttribute('route');

        if ($route) {

            $route_name = $route->getName();

            $uri_page_parameter = $request->getParam('page');

            // We want to retrieve the whole url including all the get parameters to get the current url itself
            // Excluding page (pagination) parameter.
            if ($uri_page_parameter != '') {
                $uri = str_replace(['?page=' . $uri_page_parameter, '&page=' . $uri_page_parameter], '', $uri);
            }

            // We'll also check if the request has been sent using get method
            $uri_request_sent = explode('?', $uri);

            // Route Information
            $this->container->view->getEnvironment()->addGlobal('uri', [
                'link' => $uri,
                'request_sent' => (isset($uri_request_sent[1])) ? true : false
            ]);
            $this->container->view->getEnvironment()->addGlobal('current_route', $route_name);
            $this->container->view->getEnvironment()->addGlobal('current_path', $current_path);
        }

        $response = $next($request, $response);
        return $response;
    }
}

What it does is, every time you searched for something, when you change pages, all the request that you sent will not be gone.

in your controller, you can do this:

$routes = $this->route->paginate(5);
$window = Illuminate\Pagination\UrlWindow::make($routes);
$elements = [
    $window['first'],
    is_array($window['slider']) ? '...' : null,
    $window['slider'],
    is_array($window['last']) ? '...' : null,
    $window['last'],
];
// $this->twig is an instance of \Twig_Environment
// before render the template below, you should add `is_array` and `is_string` funcion to twig first
$this->twig->addFunction('is_string', new \Twig_SimpleFunction('is_string', function ($val) {
    return is_string($val);
}));
$this->twig->addFunction('is_array', new \Twig_SimpleFunction('is_array', function ($val) {
    return is_array($val);
}));
echo $this->twig->render(
        'pageNav.html',
        ['paginator' => $routes, 'elements' => array_filter($elements)]
    );

your template file(pageNav.html) will be like this:

{% if paginator.hasPages %}
<ul class="am-pagination">
    {% if paginator.onFirstPage %}
    <li class="am-disabled"><span>&laquo;</span></li>
    {% else %}
    <li><a href="{{ paginator.previousPageUrl }}" rel="prev">&laquo;</a></li>
    {% endif %}

    {% for element in elements %}
        {% if is_string(element) %}
        <li class="am-disabled"><span>{{ element }}</span></li>
        {% endif %}

        {% if is_array(element) %}
            {% for page, url in element %}
            {% if paginator.currentPage == page %}
            <li class="am-active"><span>{{ page }}</span></li>
            {% else %}
            <li><a href="{{ url }}">{{ page }}</a></li>
            {% endif %}
            {% endfor %}
        {% endif %}
    {% endfor %}

    {% if paginator.hasMorePages %}
    <li><a href="{{ paginator.nextPageUrl }}" rel="next">&raquo;</a></li>
    {% else %}
    <li class="am-disabled"><span>&raquo;</span></li>
    {% endif %}
</ul>
{% endif %}

good luck:)

there is a little problem:

{% if (data.lastPage - data.currentPage) < half_total_links %}
    {% set from = (half_total_links - (data.lastPage - data.currentPage) - 1) - to %}
{% endif %}

when data.lastPage and data.currentPage are equal, the variable from is negative.

I have fixed so:

{% if from < 0 %}
  {% set from = pagination.page - half_total_links %}
{% endif %}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM