Completed
Pull Request — master (#57)
by
unknown
01:30
created

PaginateRoute::getLeftPoint()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4888
c 0
b 0
f 0
cc 5
nc 12
nop 1
1
<?php
2
3
namespace Spatie\PaginateRoute;
4
5
use Illuminate\Routing\Router;
6
use Illuminate\Translation\Translator;
7
use Illuminate\Support\Facades\Request;
8
use Illuminate\Routing\RouteParameterBinder;
9
use Illuminate\Contracts\Pagination\Paginator;
10
use Illuminate\Contracts\Routing\UrlGenerator;
11
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
12
13
class PaginateRoute
14
{
15
    /**
16
     * @var \Illuminate\Translation\Translator
17
     */
18
    protected $translator;
19
20
    /**
21
     * @var \Illuminate\Routing\Router
22
     */
23
    protected $router;
24
25
    /**
26
     * @var \Illuminate\Contracts\Routing\UrlGenerator
27
     */
28
    protected $urlGenerator;
29
30
    /**
31
     * @var string
32
     */
33
    protected $pageKeyword;
34
35
    /**
36
     * @param \Illuminate\Translation\Translator         $translator
37
     * @param \Illuminate\Routing\Router                 $router
38
     * @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator
39
     */
40
    public function __construct(Translator $translator, Router $router, UrlGenerator $urlGenerator)
41
    {
42
        $this->translator = $translator;
43
        $this->router = $router;
44
        $this->urlGenerator = $urlGenerator;
45
46
        // Unfortunately we can't do this in the service provider since routes are booted first
47
        $this->translator->addNamespace('paginateroute', __DIR__.'/../resources/lang');
48
49
        $this->pageKeyword = $this->translator->get('paginateroute::paginateroute.page');
50
    }
51
52
    /**
53
     * Return the current page.
54
     *
55
     * @return int
56
     */
57
    public function currentPage()
58
    {
59
        $currentRoute = $this->router->getCurrentRoute();
60
61
        if (! $currentRoute) {
62
            return 1;
63
        }
64
65
        $query = $currentRoute->parameter('pageQuery');
66
67
        return (int) str_replace($this->pageKeyword.'/', '', $query) ?: 1;
68
    }
69
70
    /**
71
     * Check if the given page is the current page.
72
     *
73
     * @param int $page
74
     *
75
     * @return bool
76
     */
77
    public function isCurrentPage($page)
78
    {
79
        return $this->currentPage() === $page;
80
    }
81
82
    /**
83
     * Get the next page number.
84
     *
85
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
86
     *
87
     * @return string|null
88
     */
89
    public function nextPage(Paginator $paginator)
90
    {
91
        if (! $paginator->hasMorePages()) {
92
            return;
93
        }
94
95
        return $this->currentPage() + 1;
96
    }
97
98
    /**
99
     * Determine wether there is a next page.
100
     *
101
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
102
     *
103
     * @return bool
104
     */
105
    public function hasNextPage(Paginator $paginator)
106
    {
107
        return $this->nextPage($paginator) !== null;
108
    }
109
110
    /**
111
     * Get the next page URL.
112
     *
113
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
114
     *
115
     * @return string|null
116
     */
117
    public function nextPageUrl(Paginator $paginator)
118
    {
119
        $nextPage = $this->nextPage($paginator);
120
121
        if ($nextPage === null) {
122
            return;
123
        }
124
125
        return $this->pageUrl($nextPage);
126
    }
127
128
    /**
129
     * Get the previous page number.
130
     *
131
     * @return string|null
132
     */
133
    public function previousPage()
134
    {
135
        if ($this->currentPage() <= 1) {
136
            return;
137
        }
138
139
        return $this->currentPage() - 1;
140
    }
141
142
    /**
143
     * Determine wether there is a previous page.
144
     *
145
     * @return bool
146
     */
147
    public function hasPreviousPage()
148
    {
149
        return $this->previousPage() !== null;
150
    }
151
152
    /**
153
     * Get the previous page URL.
154
     *
155
     * @param bool $full Return the full version of the URL in for the first page
156
     *                   Ex. /users/page/1 instead of /users
157
     *
158
     * @return string|null
159
     */
160
    public function previousPageUrl($full = false)
161
    {
162
        $previousPage = $this->previousPage();
163
164
        if ($previousPage === null) {
165
            return;
166
        }
167
168
        return $this->pageUrl($previousPage, $full);
169
    }
170
    /**
171
     * Get all urls in an array.
172
     *
173
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
174
     * @param bool                                                  $full      Return the full version of the URL in for the first page
175
     *                                                                         Ex. /users/page/1 instead of /users
176
     *
177
     * @return array
178
     */
179
    public function allUrls(LengthAwarePaginator $paginator, $full = false)
180
    {
181
        if (! $paginator->hasPages()) {
182
            return [];
183
        }
184
185
        $urls = [];
186
        $left = $this->getLeftPoint($paginator);
187
        $right = $this->getRightPoint($paginator);
188
        for ($page = $left; $page <= $right; $page++) {
189
            $urls[$page] = $this->pageUrl($page, $full);
190
        }
191
192
        return $urls;
193
    }
194
195
196
    /**
197
     * Get the left most point in the pagination element
198
     * 
199
     * @param LengthAwarePaginator $paginator
200
     * @return int
201
     */
202
    public function getLeftPoint(LengthAwarePaginator $paginator)
203
    {
204
        $side = $paginator->onEachSide;
0 ignored issues
show
Bug introduced by
Accessing onEachSide on the interface Illuminate\Contracts\Pag...on\LengthAwarePaginator suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
205
        $current = $paginator->currentPage();
206
        $last = $paginator->lastPage();
207
        
208
        if (!empty($side)) {
209
            $x = $current + $side;
210
            $offset = $x > $last ? $x - $last : 0;
211
            $left = $current - $side - $offset;
212
        }
213
        
214
        return !isset($left) || $left < 1 ? 1 : $left;
0 ignored issues
show
Bug introduced by
The variable $left does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
215
    }
216
217
218
    /**
219
     * Get the right or last point of the pagination element
220
     * 
221
     * @param LengthAwarePaginator $paginator
222
     * @return int
223
     */
224
    public function getRightPoint(LengthAwarePaginator $paginator)
225
    {
226
        $side = $paginator->onEachSide;
0 ignored issues
show
Bug introduced by
Accessing onEachSide on the interface Illuminate\Contracts\Pag...on\LengthAwarePaginator suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
227
        $current = $paginator->currentPage();
228
        $last = $paginator->lastPage();
229
230
        if (!empty($side)) {
231
            $offset = $current < $side ? $side - $current + 1 : 0;
232
            $right = $current + $side + $offset;
233
        }
234
235
        return !isset($right) || $right > $last ? $last : $right;
0 ignored issues
show
Bug introduced by
The variable $right does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
236
    }
237
    
238
239
    /**
240
     * Render a plain html list with previous, next and all urls. The current page gets a current class on the list item.
241
     *
242
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
243
     * @param bool                                                  $full              Return the full version of the URL in for the first page
244
     *                                                                                 Ex. /users/page/1 instead of /users
245
     * @param string                                                $class             Include class on pagination list
246
     *                                                                                 Ex. <ul class="pagination">
247
     * @param bool                                                  $additionalLinks   Include prev and next links on pagination list
248
     *
249
     * @return string
250
     */
251
    public function renderPageList(LengthAwarePaginator $paginator, $full = false, $class = null, $additionalLinks = false)
252
    {
253
        $urls = $this->allUrls($paginator, $full);
254
255
        if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
256
            $class = " class=\"$class\"";
257
        }
258
259
        $listItems = "<ul{$class}>";
260
261
        if ($this->hasPreviousPage() && $additionalLinks) {
262
            $listItems .= "<li><a href=\"{$this->previousPageUrl()}\">&laquo;</a></li>";
263
        }
264
265
        foreach ($urls as $i => $url) {
266
            $pageNum = $i;
267
            $css = '';
268
269
            $link = "<a href=\"{$url}\">{$pageNum}</a>";
270
            if ($pageNum == $this->currentPage()) {
271
                $css = ' class="active"';
272
                $link = $pageNum;
273
            }
274
275
            $listItems .= "<li{$css}>$link</li>";
276
        }
277
278
        if ($this->hasNextPage($paginator) && $additionalLinks) {
279
            $listItems .= "<li><a href=\"{$this->nextPageUrl($paginator)}\">&raquo;</a></li>";
280
        }
281
282
        $listItems .= '</ul>';
283
284
        return $listItems;
285
    }
286
287
    /**
288
     * Render html link tags for SEO indication of previous and next page.
289
     *
290
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
291
     * @param bool                                                  $full       Return the full version of the URL in for the first page
292
     *                                                                          Ex. /users/page/1 instead of /users
293
     *
294
     * @return string
295
     */
296
    public function renderRelLinks(LengthAwarePaginator $paginator, $full = false)
297
    {
298
        $urls = $this->allUrls($paginator, $full);
299
300
        $linkItems = '';
301
302
        foreach ($urls as $i => $url) {
303
            $pageNum = $i + 1;
304
305
            switch ($pageNum - $this->currentPage()) {
306
                case -1:
307
                    $linkItems .= "<link rel=\"prev\" href=\"{$url}\" />";
308
                    break;
309
                case 1:
310
                    $linkItems .= "<link rel=\"next\" href=\"{$url}\" />";
311
                    break;
312
            }
313
        }
314
315
        return $linkItems;
316
    }
317
318
    /**
319
     * @deprecated in favor of renderPageList.
320
     *
321
     * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
322
     * @param bool                                                  $full      Return the full version of the URL in for the first page
323
     *                                                                         Ex. /users/page/1 instead of /users
324
     *
325
     * @return string
326
     */
327
    public function renderHtml(LengthAwarePaginator $paginator, $full = false)
328
    {
329
        return $this->renderPageList($paginator, $full);
330
    }
331
332
    /**
333
     * Generate a page URL, based on the request's current URL.
334
     *
335
     * @param int  $page
336
     * @param bool $full Return the full version of the URL in for the first page
337
     *                   Ex. /users/page/1 instead of /users
338
     *
339
     * @return string
340
     */
341
    public function pageUrl($page, $full = false)
342
    {
343
        $currentPageUrl = $this->router->getCurrentRoute()->uri();
344
345
        $url = $this->addPageQuery(str_replace('{pageQuery?}', '', $currentPageUrl), $page, $full);
346
347
        foreach ((new RouteParameterBinder($this->router->getCurrentRoute()))->parameters(app('request')) as $parameterName => $parameterValue) {
348
            $url = str_replace(['{'.$parameterName.'}', '{'.$parameterName.'?}'], $parameterValue, $url);
349
        }
350
351
        $query = Request::getQueryString();
352
353
        $query = $query
354
            ? '?'.$query
355
            : '';
356
357
        return $this->urlGenerator->to($url).$query;
358
    }
359
360
    /**
361
     * Append the page query to a URL.
362
     *
363
     * @param string $url
364
     * @param int    $page
365
     * @param bool   $full Return the full version of the URL in for the first page
366
     *                     Ex. /users/page/1 instead of /users
367
     *
368
     * @return string
369
     */
370
    public function addPageQuery($url, $page, $full = false)
371
    {
372
        // If the first page's URL is requested and $full is set to false, there's nothing to be added.
373
        if ($page === 1 && ! $full) {
374
            return $url;
375
        }
376
377
        return trim($url, '/')."/{$this->pageKeyword}/{$page}";
378
    }
379
380
    /**
381
     * Register the Route::paginate macro.
382
     */
383
    public function registerMacros()
384
    {
385
        $pageKeyword = $this->pageKeyword;
386
        $router = $this->router;
387
388
        $router->macro('paginate', function ($uri, $action) use ($pageKeyword, $router) {
389
            $route = null;
390
391
            $router->group(
392
                ['middleware' => 'Spatie\PaginateRoute\SetPageMiddleware'],
393
                function () use ($pageKeyword, $router, $uri, $action, &$route) {
394
                    $route = $router->get($uri.'/{pageQuery?}', $action)->where('pageQuery', $pageKeyword.'/[0-9]+');
395
                });
396
397
            return $route;
398
        });
399
    }
400
}
401