├── CHANGELOG.md ├── LICENSE ├── README.md ├── code_of_conduct.md ├── composer.json └── src └── Pagerfanta ├── Adapter ├── AdapterInterface.php ├── ArrayAdapter.php ├── CallbackAdapter.php ├── ConcatenationAdapter.php ├── DoctrineCollectionAdapter.php ├── DoctrineDbalAdapter.php ├── DoctrineDbalSingleTableAdapter.php ├── DoctrineODMMongoDBAdapter.php ├── DoctrineODMPhpcrAdapter.php ├── DoctrineORMAdapter.php ├── DoctrineSelectableAdapter.php ├── ElasticaAdapter.php ├── FixedAdapter.php ├── MandangoAdapter.php ├── MongoAdapter.php ├── NullAdapter.php ├── Propel2Adapter.php ├── PropelAdapter.php └── SolariumAdapter.php ├── Exception ├── Exception.php ├── InvalidArgumentException.php ├── LessThan1CurrentPageException.php ├── LessThan1MaxPerPageException.php ├── LogicException.php ├── NotBooleanException.php ├── NotIntegerCurrentPageException.php ├── NotIntegerException.php ├── NotIntegerMaxPerPageException.php ├── NotValidCurrentPageException.php ├── NotValidMaxPerPageException.php └── OutOfRangeCurrentPageException.php ├── Pagerfanta.php ├── PagerfantaInterface.php └── View ├── DefaultView.php ├── OptionableView.php ├── SemanticUiView.php ├── Template ├── DefaultTemplate.php ├── SemanticUiTemplate.php ├── Template.php ├── TemplateInterface.php ├── TwitterBootstrap3Template.php ├── TwitterBootstrap4Template.php └── TwitterBootstrapTemplate.php ├── TwitterBootstrap3View.php ├── TwitterBootstrap4View.php ├── TwitterBootstrapView.php ├── ViewFactory.php ├── ViewFactoryInterface.php └── ViewInterface.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.0.2 (-) 2 | 3 | * Added `DoctrineODMPhpcrAdapter` 4 | * Added endpoint to `SolariumAdapter` 5 | * Added $useOutputWalkers mode into the DoctrineORM adapter 6 | 7 | ### 1.0.1 (2013-09-23) 8 | 9 | * Added `TwitterBootstrap3View` 10 | * Made `getResultSet` public in the `SolariumAdapter` 11 | * Fixed the `last` method in the `DefaultView` to call the last method of the template 12 | * Added `currentPageOffsetStart` and `currentPageOffsetEnd` to `Pagerfanta` 13 | * Fixed the minimum number of pages to 1 14 | 15 | ### 1.0.0 (2013-04-24) 16 | 17 | * Initial release 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 White October Ltd 2 | http://www.whiteoctober.co.uk/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NB** This project is no longer maintained; you may like to use https://github.com/BabDev/Pagerfanta instead. 2 | 3 | # Pagerfanta 4 | 5 | [![Build Status](https://travis-ci.org/whiteoctober/Pagerfanta.png?branch=master)](https://travis-ci.org/whiteoctober/Pagerfanta) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/whiteoctober/Pagerfanta/badges/quality-score.png?s=1ee480491644c07812b5206cf07d33a5035d0118)](https://scrutinizer-ci.com/g/whiteoctober/Pagerfanta/) [![Code Coverage](https://scrutinizer-ci.com/g/whiteoctober/Pagerfanta/badges/coverage.png?s=284be0616a9ba0439ee1123bcaf5fb3f6bfb0e50)](https://scrutinizer-ci.com/g/whiteoctober/Pagerfanta/) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/9e710230-b088-4904-baef-5f5e2d62e681/mini.png)](https://insight.sensiolabs.com/projects/9e710230-b088-4904-baef-5f5e2d62e681) [![Latest Stable Version](https://poser.pugx.org/pagerfanta/pagerfanta/v/stable.png)](https://packagist.org/packages/pagerfanta/pagerfanta) [![Total Downloads](https://poser.pugx.org/pagerfanta/pagerfanta/downloads.png)](https://packagist.org/packages/pagerfanta/pagerfanta) 6 | 7 | This project is for PHP 7. 8 | If you need support for PHP < 7, use [Release v1.1.0](https://github.com/whiteoctober/Pagerfanta/releases/tag/v1.1.0). 9 | 10 | ## Usage 11 | 12 | ```php 13 | setMaxPerPage($maxPerPage); // 10 by default 22 | $maxPerPage = $pagerfanta->getMaxPerPage(); 23 | 24 | $pagerfanta->setCurrentPage($currentPage); // 1 by default 25 | $currentPage = $pagerfanta->getCurrentPage(); 26 | 27 | $nbResults = $pagerfanta->getNbResults(); 28 | $currentPageResults = $pagerfanta->getCurrentPageResults(); 29 | ``` 30 | 31 | Some of the other methods available: 32 | 33 | ```php 34 | $pagerfanta->getNbPages(); 35 | $pagerfanta->haveToPaginate(); // whether the number of results is higher than the max per page 36 | $pagerfanta->hasPreviousPage(); 37 | $pagerfanta->getPreviousPage(); 38 | $pagerfanta->hasNextPage(); 39 | $pagerfanta->getNextPage(); 40 | $pagerfanta->getCurrentPageOffsetStart(); 41 | $pagerfanta->getCurrentPageOffsetEnd(); 42 | ``` 43 | 44 | ### Changing the page based on user selection 45 | 46 | If you're using the example route-generator function shown below, 47 | the page selected by the user will be available in the `page` GET (querystring) parameter. 48 | 49 | You would then need to call `setCurrentPage` with the value of that parameter: 50 | 51 | ```php 52 | if (isset($_GET["page"])) { 53 | $pagerfanta->setCurrentPage($_GET["page"]); 54 | } 55 | ``` 56 | 57 | ### setMaxPerPage and setCurrentPage 58 | 59 | The `->setMaxPerPage()` and `->setCurrentPage()` methods implement 60 | a fluent interface: 61 | 62 | ```php 63 | setMaxPerPage($maxPerPage) 67 | ->setCurrentPage($currentPage); 68 | ``` 69 | 70 | The `->setMaxPerPage()` method throws an exception if the max per page 71 | is not valid: 72 | 73 | * `Pagerfanta\Exception\NotIntegerMaxPerPageException` 74 | * `Pagerfanta\Exception\LessThan1MaxPerPageException` 75 | 76 | Both extend from `Pagerfanta\Exception\NotValidMaxPerPageException`. 77 | 78 | The `->setCurrentPage()` method throws an exception if the page is not valid: 79 | 80 | * `Pagerfanta\Exception\NotIntegerCurrentPageException` 81 | * `Pagerfanta\Exception\LessThan1CurrentPageException` 82 | * `Pagerfanta\Exception\OutOfRangeCurrentPageException` 83 | 84 | All of them extend from `Pagerfanta\Exception\NotValidCurrentPageException`. 85 | 86 | `->setCurrentPage()` throws an out ot range exception depending on the 87 | max per page, so if you are going to modify the max per page, you should do it 88 | before setting the current page. 89 | 90 | (If you want to use Pagerfanta in a Symfony project, see 91 | [https://github.com/whiteoctober/WhiteOctoberPagerfantaBundle](https://github.com/whiteoctober/WhiteOctoberPagerfantaBundle).) 92 | 93 | ## Adapters 94 | 95 | The adapter's concept is very simple. An adapter just returns the number 96 | of results and an slice for a offset and length. This way you can adapt 97 | a pagerfanta to paginate any kind results simply by creating an adapter. 98 | 99 | An adapter must implement the `Pagerfanta\Adapter\AdapterInterface` 100 | interface, which has these two methods: 101 | 102 | ```php 103 | find(); 147 | $adapter = new MongoAdapter($cursor); 148 | ``` 149 | 150 | ### MandangoAdapter 151 | 152 | To paginate [Mandango](http://mandango.org) Queries. 153 | 154 | ```php 155 | getRepository('Model\Article')->createQuery(); 160 | $adapter = new MandangoAdapter($query); 161 | ``` 162 | 163 | ### DoctrineDbalAdapter 164 | 165 | To paginate [DoctrineDbal](http://www.doctrine-project.org/projects/dbal.html) 166 | query builders. 167 | 168 | ```php 169 | select('p.*')->from('posts', 'p'); 176 | 177 | $countQueryBuilderModifier = function ($queryBuilder) { 178 | $queryBuilder->select('COUNT(DISTINCT p.id) AS total_results') 179 | ->setMaxResults(1); 180 | }; 181 | 182 | $adapter = new DoctrineDbalAdapter($queryBuilder, $countQueryBuilderModifier); 183 | ``` 184 | 185 | ### DoctrineDbalSingleTableAdapter 186 | 187 | To simplify the pagination of single table 188 | [DoctrineDbal](http://www.doctrine-project.org/projects/dbal.html) 189 | query builders. 190 | 191 | This adapter only paginates single table query builders, without joins. 192 | 193 | ```php 194 | select('p.*')->from('posts', 'p'); 201 | 202 | $countField = 'p.id'; 203 | 204 | $adapter = new DoctrineDbalSingleTableAdapter($queryBuilder, $countField); 205 | ``` 206 | 207 | ### DoctrineORMAdapter 208 | 209 | To paginate [DoctrineORM](http://www.doctrine-project.org/projects/orm) query objects. 210 | 211 | ```php 212 | createQueryBuilder() 217 | ->select('u') 218 | ->from('Model\Article', 'u'); 219 | $adapter = new DoctrineORMAdapter($queryBuilder); 220 | ``` 221 | 222 | ### DoctrineODMMongoDBAdapter 223 | 224 | To paginate [DoctrineODMMongoDB](http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/) query builders. 225 | 226 | ```php 227 | createQueryBuilder('Model\Article'); 232 | $adapter = new DoctrineODMMongoDBAdapter($queryBuilder); 233 | ``` 234 | 235 | ### DoctrineODMPhpcrAdapter 236 | 237 | To paginate [Doctrine PHPCR-ODM](http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/) query builders. 238 | 239 | ```php 240 | createQueryBuilder(); 245 | $queryBuilder->from('Model\Article'); 246 | $adapter = new DoctrineODMPhpcrAdapter($queryBuilder); 247 | ``` 248 | 249 | ### DoctrineCollectionAdapter 250 | 251 | To paginate a `Doctrine\Common\Collection\Collections` interface 252 | you can use the `DoctrineCollectionAdapter`. It proxies to the 253 | count() and slice() methods on the Collections interface for 254 | pagination. This makes sense if you are using Doctrine ORMs Extra 255 | Lazy association features: 256 | 257 | ```php 258 | find("Pagerfanta\Tests\Adapter\DoctrineORM\User", 1); 263 | 264 | $adapter = new DoctrineCollectionAdapter($user->getGroups()); 265 | ``` 266 | 267 | ### DoctrineSelectableAdapter 268 | 269 | To paginate a `Doctrine\Common\Collection\Selectable` interface 270 | you can use the `DoctrineSelectableAdapter`. It uses the matching() 271 | method on the Selectable interface for pagination. This is 272 | especially usefull when using the Doctrine Criteria object to 273 | filter a PersistentCollection: 274 | 275 | ```php 276 | find("Pagerfanta\Tests\Adapter\DoctrineORM\User", 1); 282 | $comments = $user->getComments(); 283 | $criteria = Criteria::create()->andWhere(Criteria::expr()->in('id', array(1,2,3)); 284 | 285 | $adapter = new DoctrineSelectableAdapter($comments, $criteria); 286 | ``` 287 | 288 | Note that you should never use this adapter with a 289 | PersistentCollection which is not set to use the EXTRA_LAZY fetch mode. 290 | 291 | *Be careful when using the `count()` method, currently Doctrine2 292 | needs to fetch all the records to count the number of elements.* 293 | 294 | ### ElasticaAdapter 295 | 296 | To paginate an Elastica Query query: 297 | 298 | ```php 299 | 'Fred' 311 | )); 312 | 313 | $adapter = new ElasticaAdapter($searchable, $query); 314 | ``` 315 | 316 | *Be careful when paginating a huge set of documents. By default, offset + limit 317 | can't exceed 10000. You can mitigate this by setting the `$maxResults` 318 | parameter when constructing the `ElasticaAdapter`. For more information, see: 319 | [#213](https://github.com/whiteoctober/Pagerfanta/pull/213#issue-87631892).* 320 | 321 | ### PropelAdapter 322 | 323 | To paginate a propel 1 query: 324 | 325 | ```php 326 | createSelect(); 355 | $query->setQuery('search term'); 356 | 357 | $adapter = new SolariumAdapter($solarium, $query); 358 | ``` 359 | 360 | ### FixedAdapter 361 | 362 | Best used when you need to do a custom paging solution and 363 | don't want to implement a full adapter for a one-off use case. 364 | 365 | It returns always the same data no matter what page you query: 366 | 367 | ```php 368 | 3); 448 | $html = $view->render($pagerfanta, $routeGenerator, $options); 449 | ``` 450 | 451 | Options (default): 452 | 453 | * proximity (3) 454 | * prev_message (Previous) 455 | * next_message (Next) 456 | * css_disabled_class (disabled) 457 | * css_dots_class (dots) 458 | * css_current_class (current) 459 | * dots_text (...) 460 | * container_template () 461 | * page_template (%text%) 462 | * span_template (%text%) 463 | 464 | CSS: 465 | 466 | ```css 467 | .pagerfanta { 468 | } 469 | 470 | .pagerfanta a, 471 | .pagerfanta span { 472 | display: inline-block; 473 | border: 1px solid blue; 474 | color: blue; 475 | margin-right: .2em; 476 | padding: .25em .35em; 477 | } 478 | 479 | .pagerfanta a { 480 | text-decoration: none; 481 | } 482 | 483 | .pagerfanta a:hover { 484 | background: #ccf; 485 | } 486 | 487 | .pagerfanta .dots { 488 | border-width: 0; 489 | } 490 | 491 | .pagerfanta .current { 492 | background: #ccf; 493 | font-weight: bold; 494 | } 495 | 496 | .pagerfanta .disabled { 497 | border-color: #ccf; 498 | color: #ccf; 499 | } 500 | 501 | COLORS: 502 | 503 | .pagerfanta a, 504 | .pagerfanta span { 505 | border-color: blue; 506 | color: blue; 507 | } 508 | 509 | .pagerfanta a:hover { 510 | background: #ccf; 511 | } 512 | 513 | .pagerfanta .current { 514 | background: #ccf; 515 | } 516 | 517 | .pagerfanta .disabled { 518 | border-color: #ccf; 519 | color: #cf; 520 | } 521 | ``` 522 | 523 | ### TwitterBootstrapView, TwitterBootstrap3View and TwitterBootstrap4View 524 | 525 | These views generate paginators designed for use with 526 | [Twitter Bootstrap](https://github.com/twitter/bootstrap). 527 | 528 | `TwitterBootstrapView` is for Bootstrap 2; `TwitterBootstrap3View` is for Bootstrap 3; `TwitterBootstrap4View` is for Bootstrap 4 (alpha). 529 | 530 | ```php 531 | 3); 537 | $html = $view->render($pagerfanta, $routeGenerator, $options); 538 | ``` 539 | 540 | Options (default): 541 | 542 | * proximity (3) 543 | * prev_message (← Previous) 544 | * prev_disabled_href () 545 | * next_message (Next →) 546 | * next_disabled_href () 547 | * dots_message (…) 548 | * dots_href () 549 | * css_container_class (pagination) 550 | * css_prev_class (prev) 551 | * css_next_class (next) 552 | * css_disabled_class (disabled) 553 | * css_dots_class (disabled) 554 | * css_active_class (active) 555 | 556 | ### SemanticUiView 557 | 558 | This view generates a pagination for 559 | [Semantic UI](https://github.com/Semantic-Org/Semantic-UI). 560 | 561 | ```php 562 | 3); 568 | $html = $view->render($pagerfanta, $routeGenerator, $options); 569 | ``` 570 | 571 | Options (default): 572 | 573 | * proximity (3) 574 | * prev_message (← Previous) 575 | * prev_disabled_href () 576 | * next_message (Next →) 577 | * next_disabled_href () 578 | * dots_message (…) 579 | * dots_href () 580 | * css_container_class (pagination) 581 | * css_item_class (item) 582 | * css_prev_class (prev) 583 | * css_next_class (next) 584 | * css_disabled_class (disabled) 585 | * css_dots_class (disabled) 586 | * css_active_class (active) 587 | 588 | ### OptionableView 589 | 590 | This view is to reuse options in different views. 591 | 592 | ```php 593 | 3)); 602 | 603 | $myView2 = new OptionableView($defaultView, array('prev_message' => 'Anterior', 'next_message' => 'Siguiente')); 604 | 605 | // using in a normal way 606 | $pagerfantaHtml = $myView2->render($pagerfanta, $routeGenerator); 607 | 608 | // overwriting default options 609 | $pagerfantaHtml = $myView2->render($pagerfanta, $routeGenerator, array('next_message' => 'Siguiente!!')); 610 | ``` 611 | 612 | ## Contributing 613 | 614 | We welcome contributions to this project, including pull requests and issues (and discussions on existing issues). 615 | 616 | If you'd like to contribute code but aren't sure what, the [issues list](https://github.com/whiteoctober/pagerfanta/issues) is a good place to start. 617 | If you're a first-time code contributor, you may find Github's guide to [forking projects](https://guides.github.com/activities/forking/) helpful. 618 | 619 | All contributors (whether contributing code, involved in issue discussions, or involved in any other way) must abide by our [code of conduct](code_of_conduct.md). 620 | 621 | ## Acknowledgements 622 | 623 | Pagerfanta is inspired by [Zend Paginator](https://github.com/zendframework/zf2). 624 | 625 | Thanks also to Pablo Díez (pablodip@gmail.com) for most of the work on the first versions of Pagerfanta. 626 | 627 | ## Licence 628 | 629 | Pagerfanta is licensed under the [MIT License](LICENSE). 630 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | This project's code-of-conduct can be found at [https://github.com/whiteoctober/open-source-code-of-conduct/blob/master/code_of_conduct.md](https://github.com/whiteoctober/open-source-code-of-conduct/blob/master/code_of_conduct.md). 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pagerfanta/pagerfanta", 3 | "description": "Pagination for PHP", 4 | "keywords": ["page","paging", "paginator", "pagination"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Díez", 10 | "email": "pablodip@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^7.0" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "^6.5", 18 | "doctrine/orm": "~2.3", 19 | "mandango/mandango": "~1.0@dev", 20 | "mandango/mondator": "~1.0@dev", 21 | "jmikola/geojson": "~1.0", 22 | "doctrine/phpcr-odm": "1.*", 23 | "propel/propel": "~2.0@dev", 24 | "propel/propel1": "~1.6", 25 | "ruflin/Elastica": "~1.3", 26 | "solarium/solarium": "~3.1", 27 | "jackalope/jackalope-doctrine-dbal": "1.*" 28 | }, 29 | "suggest": { 30 | "doctrine/orm": "To use the DoctrineORMAdapter.", 31 | "mandango/mandango": "To use the MandangoAdapter.", 32 | "doctrine/mongodb-odm": "To use the DoctrineODMMongoDBAdapter.", 33 | "doctrine/phpcr-odm": "To use the DoctrineODMPhpcrAdapter. >= 1.1.0", 34 | "propel/propel1": "To use the PropelAdapter", 35 | "propel/propel": "To use the Propel2Adapter", 36 | "solarium/solarium": "To use the SolariumAdapter." 37 | }, 38 | "autoload": { 39 | "psr-4": { "Pagerfanta\\": "src/Pagerfanta/" } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { "Pagerfanta\\Tests\\": "tests/Pagerfanta/Tests/" } 43 | }, 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "1.0.x-dev" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * AdapterInterface. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | interface AdapterInterface 20 | { 21 | /** 22 | * Returns the number of results. 23 | * 24 | * @return integer The number of results. 25 | */ 26 | public function getNbResults(); 27 | 28 | /** 29 | * Returns an slice of the results. 30 | * 31 | * @param integer $offset The offset. 32 | * @param integer $length The length. 33 | * 34 | * @return array|\Traversable The slice. 35 | */ 36 | public function getSlice($offset, $length); 37 | } 38 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/ArrayAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * ArrayAdapter. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class ArrayAdapter implements AdapterInterface 20 | { 21 | private $array; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param array $array The array. 27 | */ 28 | public function __construct(array $array) 29 | { 30 | $this->array = $array; 31 | } 32 | 33 | /** 34 | * Returns the array. 35 | * 36 | * @return array The array. 37 | */ 38 | public function getArray() 39 | { 40 | return $this->array; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getNbResults() 47 | { 48 | return count($this->array); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function getSlice($offset, $length) 55 | { 56 | return array_slice($this->array, $offset, $length); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/CallbackAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Pagerfanta\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * @author Adrien Brault 18 | */ 19 | class CallbackAdapter implements AdapterInterface 20 | { 21 | private $getNbResultsCallback; 22 | private $getSliceCallback; 23 | 24 | /** 25 | * @param callable $getNbResultsCallback 26 | * @param callable $getSliceCallback 27 | */ 28 | public function __construct($getNbResultsCallback, $getSliceCallback) 29 | { 30 | if (!is_callable($getNbResultsCallback)) { 31 | throw new InvalidArgumentException('$getNbResultsCallback should be a callable'); 32 | } 33 | if (!is_callable($getSliceCallback)) { 34 | throw new InvalidArgumentException('$getSliceCallback should be a callable'); 35 | } 36 | 37 | $this->getNbResultsCallback = $getNbResultsCallback; 38 | $this->getSliceCallback = $getSliceCallback; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function getNbResults() 45 | { 46 | return call_user_func($this->getNbResultsCallback); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getSlice($offset, $length) 53 | { 54 | return call_user_func($this->getSliceCallback, $offset, $length); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/ConcatenationAdapter.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ConcatenationAdapter implements AdapterInterface 13 | { 14 | /** 15 | * @var AdapterInterface[] List of adapters 16 | */ 17 | protected $adapters; 18 | 19 | /** 20 | * @var int[]|null Cache of the numbers of results of the adapters. The indexes correspond the indexes of the 21 | * `adapters` property. 22 | */ 23 | protected $adaptersNbResultsCache; 24 | 25 | /** 26 | * @param AdapterInterface[] $adapters 27 | * @throws InvalidArgumentException 28 | */ 29 | public function __construct(array $adapters) 30 | { 31 | foreach ($adapters as $index => $adapter) { 32 | if (!($adapter instanceof AdapterInterface)) { 33 | throw new InvalidArgumentException(sprintf( 34 | 'Argument $adapters[%s] expected to be a \Pagerfanta\Adapter\AdapterInterface instance, a %s given', 35 | $index, 36 | is_object($adapter) ? sprintf('%s instance', get_class($adapter)) : gettype($adapter) 37 | )); 38 | } 39 | } 40 | 41 | $this->adapters = $adapters; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getNbResults() 48 | { 49 | if (!isset($this->adaptersNbResultsCache)) { 50 | $this->refreshAdaptersNbResults(); 51 | } 52 | 53 | return array_sum($this->adaptersNbResultsCache); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | * @return array 59 | */ 60 | public function getSlice($offset, $length) 61 | { 62 | if (!isset($this->adaptersNbResultsCache)) { 63 | $this->refreshAdaptersNbResults(); 64 | } 65 | 66 | $slice = array(); 67 | $previousAdaptersNbResultsSum = 0; 68 | $requestFirstIndex = $offset; 69 | $requestLastIndex = $offset + $length - 1; 70 | 71 | foreach ($this->adapters as $index => $adapter) { 72 | $adapterNbResults = $this->adaptersNbResultsCache[$index]; 73 | $adapterFirstIndex = $previousAdaptersNbResultsSum; 74 | $adapterLastIndex = $adapterFirstIndex + $adapterNbResults - 1; 75 | 76 | $previousAdaptersNbResultsSum += $adapterNbResults; 77 | 78 | // The adapter is fully below the requested slice range — skip it 79 | if ($adapterLastIndex < $requestFirstIndex) { 80 | continue; 81 | } 82 | 83 | // The adapter is fully above the requested slice range — finish the gathering 84 | if ($adapterFirstIndex > $requestLastIndex) { 85 | break; 86 | } 87 | 88 | // Else the adapter range definitely intersects with the requested range 89 | $fetchOffset = $requestFirstIndex - $adapterFirstIndex; 90 | $fetchLength = $length; 91 | 92 | // The requested range start is below the adapter range start 93 | if ($fetchOffset < 0) { 94 | $fetchLength += $fetchOffset; 95 | $fetchOffset = 0; 96 | } 97 | 98 | // The requested range end is above the adapter range end 99 | if ($fetchOffset + $fetchLength > $adapterNbResults) { 100 | $fetchLength = $adapterNbResults - $fetchOffset; 101 | } 102 | 103 | // Getting the subslice from the adapter and adding it to the result slice 104 | $fetchSlice = $adapter->getSlice($fetchOffset, $fetchLength); 105 | foreach ($fetchSlice as $item) { 106 | $slice[] = $item; 107 | } 108 | } 109 | 110 | return $slice; 111 | } 112 | 113 | /** 114 | * Refreshes the cache of the numbers of results of the adapters. 115 | */ 116 | protected function refreshAdaptersNbResults() 117 | { 118 | if (!isset($this->adaptersNbResultsCache)) { 119 | $this->adaptersNbResultsCache = array(); 120 | } 121 | 122 | foreach ($this->adapters as $index => $adapter) { 123 | $this->adaptersNbResultsCache[$index] = $adapter->getNbResults(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineCollectionAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\Common\Collections\Collection; 15 | 16 | /** 17 | * DoctrineCollectionAdapter. 18 | * 19 | * @author Pablo Díez 20 | */ 21 | class DoctrineCollectionAdapter implements AdapterInterface 22 | { 23 | private $collection; 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param Collection $collection A Doctrine collection. 29 | */ 30 | public function __construct(Collection $collection) 31 | { 32 | $this->collection = $collection; 33 | } 34 | 35 | /** 36 | * Returns the collection. 37 | * 38 | * @return Collection The collection. 39 | */ 40 | public function getCollection() 41 | { 42 | return $this->collection; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getNbResults() 49 | { 50 | return $this->collection->count(); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function getSlice($offset, $length) 57 | { 58 | return $this->collection->slice($offset, $length); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineDbalAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Pagerfanta\Exception\InvalidArgumentException; 16 | 17 | /** 18 | * @author Michael Williams 19 | * @author Pablo Díez 20 | */ 21 | class DoctrineDbalAdapter implements AdapterInterface 22 | { 23 | private $queryBuilder; 24 | private $countQueryBuilderModifier; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param QueryBuilder $queryBuilder A DBAL query builder. 30 | * @param callable $countQueryBuilderModifier A callable to modifier the query builder to count. 31 | */ 32 | public function __construct(QueryBuilder $queryBuilder, $countQueryBuilderModifier) 33 | { 34 | if ($queryBuilder->getType() !== QueryBuilder::SELECT) { 35 | throw new InvalidArgumentException('Only SELECT queries can be paginated.'); 36 | } 37 | 38 | if (!is_callable($countQueryBuilderModifier)) { 39 | throw new InvalidArgumentException('The count query builder modifier must be a callable.'); 40 | } 41 | 42 | $this->queryBuilder = clone $queryBuilder; 43 | $this->countQueryBuilderModifier = $countQueryBuilderModifier; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getNbResults() 50 | { 51 | $qb = $this->prepareCountQueryBuilder(); 52 | $result = $qb->execute()->fetchColumn(); 53 | 54 | return (int) $result; 55 | } 56 | 57 | private function prepareCountQueryBuilder() 58 | { 59 | $qb = clone $this->queryBuilder; 60 | call_user_func($this->countQueryBuilderModifier, $qb); 61 | 62 | return $qb; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function getSlice($offset, $length) 69 | { 70 | $qb = clone $this->queryBuilder; 71 | $result = $qb->setMaxResults($length) 72 | ->setFirstResult($offset) 73 | ->execute(); 74 | 75 | return $result->fetchAll(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineDbalSingleTableAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Pagerfanta\Exception\InvalidArgumentException; 16 | 17 | /** 18 | * @author Michael Williams 19 | * @author Pablo Díez 20 | */ 21 | class DoctrineDbalSingleTableAdapter extends DoctrineDbalAdapter 22 | { 23 | /** 24 | * Constructor. 25 | * 26 | * @param QueryBuilder $queryBuilder A DBAL query builder. 27 | * @param string $countField Primary key for the table in query. Used in count expression. Must include table alias 28 | */ 29 | public function __construct(QueryBuilder $queryBuilder, $countField) 30 | { 31 | if ($this->hasQueryBuilderJoins($queryBuilder)) { 32 | throw new InvalidArgumentException('The query builder cannot have joins.'); 33 | } 34 | 35 | $countQueryBuilderModifier = $this->createCountQueryModifier($countField); 36 | 37 | parent::__construct($queryBuilder, $countQueryBuilderModifier); 38 | } 39 | 40 | private function hasQueryBuilderJoins(QueryBuilder $queryBuilder) 41 | { 42 | $joins = $queryBuilder->getQueryPart('join'); 43 | 44 | return !empty($joins); 45 | } 46 | 47 | private function createCountQueryModifier($countField) 48 | { 49 | $select = $this->createSelectForCountField($countField); 50 | 51 | return function (QueryBuilder $queryBuilder) use ($select) { 52 | $queryBuilder->select($select) 53 | ->resetQueryPart('orderBy') 54 | ->setMaxResults(1); 55 | }; 56 | } 57 | 58 | private function createSelectForCountField($countField) 59 | { 60 | if ($this->countFieldHasNoAlias($countField)) { 61 | throw new InvalidArgumentException('The $countField must contain a table alias in the string.'); 62 | } 63 | 64 | return sprintf('COUNT(DISTINCT %s) AS total_results', $countField); 65 | } 66 | 67 | private function countFieldHasNoAlias($countField) 68 | { 69 | return strpos($countField, '.') === false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineODMMongoDBAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\ODM\MongoDB\Query\Builder; 15 | 16 | /** 17 | * DoctrineODMMongoDBAdapter. 18 | * 19 | * @author Pablo Díez 20 | */ 21 | class DoctrineODMMongoDBAdapter implements AdapterInterface 22 | { 23 | private $queryBuilder; 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param Builder $queryBuilder A DoctrineMongo query builder. 29 | */ 30 | public function __construct(Builder $queryBuilder) 31 | { 32 | $this->queryBuilder = $queryBuilder; 33 | } 34 | 35 | /** 36 | * Returns the query builder. 37 | * 38 | * @return Builder The query builder. 39 | */ 40 | public function getQueryBuilder() 41 | { 42 | return $this->queryBuilder; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getNbResults() 49 | { 50 | $qb = clone $this->queryBuilder; 51 | 52 | return $qb 53 | ->limit(0) 54 | ->skip(0) 55 | ->count() 56 | ->getQuery() 57 | ->execute(); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function getSlice($offset, $length) 64 | { 65 | return $this->queryBuilder 66 | ->limit($length) 67 | ->skip($offset) 68 | ->getQuery() 69 | ->execute(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineODMPhpcrAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder; 15 | use Doctrine\ODM\PHPCR\Query\Query; 16 | 17 | /** 18 | * Pagerfanta adapter for Doctrine PHPCR-ODM. 19 | * 20 | * @author David Buchmann 21 | */ 22 | class DoctrineODMPhpcrAdapter implements AdapterInterface 23 | { 24 | private $queryBuilder; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param QueryBuilder $queryBuilder A Doctrine PHPCR-ODM query builder. 30 | */ 31 | public function __construct(QueryBuilder $queryBuilder) 32 | { 33 | $this->queryBuilder = $queryBuilder; 34 | } 35 | 36 | /** 37 | * Returns the query builder. 38 | * 39 | * @return QueryBuilder The query builder. 40 | */ 41 | public function getQueryBuilder() 42 | { 43 | return $this->queryBuilder; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getNbResults() 50 | { 51 | return $this->queryBuilder->getQuery()->execute(null, Query::HYDRATE_PHPCR)->getRows()->count(); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getSlice($offset, $length) 58 | { 59 | return $this->queryBuilder 60 | ->getQuery() 61 | ->setMaxResults($length) 62 | ->setFirstResult($offset) 63 | ->execute(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineORMAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; 15 | 16 | /** 17 | * DoctrineORMAdapter. 18 | * 19 | * @author Christophe Coevoet 20 | */ 21 | class DoctrineORMAdapter implements AdapterInterface 22 | { 23 | /** 24 | * @var \Doctrine\ORM\Tools\Pagination\Paginator 25 | */ 26 | private $paginator; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param \Doctrine\ORM\Query|\Doctrine\ORM\QueryBuilder $query A Doctrine ORM query or query builder. 32 | * @param boolean $fetchJoinCollection Whether the query joins a collection (true by default). 33 | * @param boolean|null $useOutputWalkers Whether to use output walkers pagination mode 34 | */ 35 | public function __construct($query, $fetchJoinCollection = true, $useOutputWalkers = null) 36 | { 37 | $this->paginator = new DoctrinePaginator($query, $fetchJoinCollection); 38 | $this->paginator->setUseOutputWalkers($useOutputWalkers); 39 | } 40 | 41 | /** 42 | * Returns the query 43 | * 44 | * @return \Doctrine\ORM\Query 45 | */ 46 | public function getQuery() 47 | { 48 | return $this->paginator->getQuery(); 49 | } 50 | 51 | /** 52 | * Returns whether the query joins a collection. 53 | * 54 | * @return boolean Whether the query joins a collection. 55 | */ 56 | public function getFetchJoinCollection() 57 | { 58 | return $this->paginator->getFetchJoinCollection(); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getNbResults() 65 | { 66 | return count($this->paginator); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function getSlice($offset, $length) 73 | { 74 | $this->paginator 75 | ->getQuery() 76 | ->setFirstResult($offset) 77 | ->setMaxResults($length); 78 | 79 | return $this->paginator->getIterator(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/DoctrineSelectableAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Doctrine\Common\Collections\Criteria; 15 | use Doctrine\Common\Collections\Selectable; 16 | 17 | /** 18 | * DoctrineSelectableAdapter. 19 | * 20 | * @author Boris Guéry 21 | */ 22 | class DoctrineSelectableAdapter implements AdapterInterface 23 | { 24 | /** 25 | * @var Selectable 26 | */ 27 | private $selectable; 28 | 29 | /** 30 | * @var Criteria 31 | */ 32 | private $criteria; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param Selectable $selectable An implementation of the Selectable interface. 38 | * @param Criteria $criteria A Doctrine criteria. 39 | */ 40 | public function __construct(Selectable $selectable, Criteria $criteria) 41 | { 42 | $this->selectable = $selectable; 43 | $this->criteria = $criteria; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getNbResults() 50 | { 51 | $firstResult = null; 52 | $maxResults = null; 53 | 54 | $criteria = $this->createCriteria($firstResult, $maxResults); 55 | 56 | return $this->selectable->matching($criteria)->count(); 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function getSlice($offset, $length) 63 | { 64 | $firstResult = $offset; 65 | $maxResults = $length; 66 | 67 | $criteria = $this->createCriteria($firstResult, $maxResults); 68 | 69 | return $this->selectable->matching($criteria); 70 | } 71 | 72 | private function createCriteria($firstResult, $maxResult) 73 | { 74 | $criteria = clone $this->criteria; 75 | $criteria->setFirstResult($firstResult); 76 | $criteria->setMaxResults($maxResult); 77 | 78 | return $criteria; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/ElasticaAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Elastica\Query; 15 | use Elastica\SearchableInterface; 16 | 17 | class ElasticaAdapter implements AdapterInterface 18 | { 19 | /** 20 | * @var Query 21 | */ 22 | private $query; 23 | 24 | /** 25 | * @var \Elastica\ResultSet 26 | */ 27 | private $resultSet; 28 | 29 | /** 30 | * @var SearchableInterface 31 | */ 32 | private $searchable; 33 | 34 | /** 35 | * @var array 36 | */ 37 | private $options; 38 | 39 | /** 40 | * @var int|null 41 | * 42 | * Used to limit the number of totalHits returned by ES. 43 | * For more information, see: https://github.com/whiteoctober/Pagerfanta/pull/213#issue-87631892 44 | */ 45 | private $maxResults; 46 | 47 | public function __construct(SearchableInterface $searchable, Query $query, array $options = array(), $maxResults = null) 48 | { 49 | $this->searchable = $searchable; 50 | $this->query = $query; 51 | $this->options = $options; 52 | $this->maxResults = $maxResults; 53 | } 54 | 55 | /** 56 | * Returns the number of results. 57 | * 58 | * @return integer The number of results. 59 | */ 60 | public function getNbResults() 61 | { 62 | if (!$this->resultSet) { 63 | $totalHits = $this->searchable->count($this->query); 64 | } else { 65 | $totalHits = $this->resultSet->getTotalHits(); 66 | } 67 | 68 | if (null === $this->maxResults) { 69 | return $totalHits; 70 | } 71 | 72 | return min($totalHits, $this->maxResults); 73 | } 74 | 75 | /** 76 | * Returns the Elastica ResultSet. Will return null if getSlice has not yet been 77 | * called. 78 | * 79 | * @return \Elastica\ResultSet|null 80 | */ 81 | public function getResultSet() 82 | { 83 | return $this->resultSet; 84 | } 85 | 86 | /** 87 | * Returns an slice of the results. 88 | * 89 | * @param integer $offset The offset. 90 | * @param integer $length The length. 91 | * 92 | * @return array|\Traversable The slice. 93 | */ 94 | public function getSlice($offset, $length) 95 | { 96 | return $this->resultSet = $this->searchable->search($this->query, array_merge($this->options, array( 97 | 'from' => $offset, 98 | 'size' => $length 99 | ))); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/FixedAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * Provides you with an adapter that returns always the same data. 16 | * 17 | * Best used when you need to do a custom paging solution and don't 18 | * want to implement a full adapter for a one-off use case. 19 | * 20 | * @author Jordi Boggiano 21 | */ 22 | class FixedAdapter implements AdapterInterface 23 | { 24 | private $nbResults; 25 | private $results; 26 | 27 | /** 28 | * @param int $nbResults 29 | * @param array|\Traversable $results 30 | */ 31 | public function __construct($nbResults, $results) 32 | { 33 | $this->nbResults = $nbResults; 34 | $this->results = $results; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | public function getNbResults() 41 | { 42 | return $this->nbResults; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | public function getSlice($offset, $length) 49 | { 50 | return $this->results; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/MandangoAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Mandango\Query; 15 | 16 | /** 17 | * MandangoAdapter. 18 | * 19 | * @author Pablo Díez 20 | */ 21 | class MandangoAdapter implements AdapterInterface 22 | { 23 | private $query; 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param Query $query The query. 29 | */ 30 | public function __construct(Query $query) 31 | { 32 | $this->query = $query; 33 | } 34 | 35 | /** 36 | * Returns the query. 37 | * 38 | * @return Query The query. 39 | */ 40 | public function getQuery() 41 | { 42 | return $this->query; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getNbResults() 49 | { 50 | return $this->query->count(); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function getSlice($offset, $length) 57 | { 58 | return $this->query->limit($length)->skip($offset)->all(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/MongoAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * MongoAdapter. 16 | * 17 | * @author Sergey Ponomaryov 18 | */ 19 | class MongoAdapter implements AdapterInterface 20 | { 21 | private $cursor; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param \MongoCursor $cursor The cursor. 27 | */ 28 | public function __construct(\MongoCursor $cursor) 29 | { 30 | $this->cursor = $cursor; 31 | } 32 | 33 | /** 34 | * Returns the cursor. 35 | * 36 | * @return \MongoCursor The cursor. 37 | */ 38 | public function getCursor() 39 | { 40 | return $this->cursor; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getNbResults() 47 | { 48 | return $this->cursor->count(); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function getSlice($offset, $length) 55 | { 56 | $this->cursor->limit($length); 57 | $this->cursor->skip($offset); 58 | 59 | return $this->cursor; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/NullAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * NullAdapter. 16 | * 17 | * @author Benjamin Dulau 18 | */ 19 | class NullAdapter implements AdapterInterface 20 | { 21 | private $nbResults; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param integer $nbResults Total item count. 27 | */ 28 | public function __construct($nbResults = 0) 29 | { 30 | $this->nbResults = (int) $nbResults; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getNbResults() 37 | { 38 | return $this->nbResults; 39 | } 40 | 41 | /** 42 | * The following methods are derived from code of the Zend Framework 43 | * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). 44 | * 45 | * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) 46 | * 47 | * {@inheritdoc} 48 | */ 49 | public function getSlice($offset, $length) 50 | { 51 | if ($offset >= $this->nbResults) { 52 | return array(); 53 | } 54 | 55 | $nullArrayLength = $this->calculateNullArrayLength($offset, $length); 56 | 57 | return $this->createNullArray($nullArrayLength); 58 | } 59 | 60 | private function calculateNullArrayLength($offset, $length) 61 | { 62 | $remainCount = $this->remainCount($offset); 63 | if ($length > $remainCount) { 64 | return $remainCount; 65 | } 66 | 67 | return $length; 68 | } 69 | 70 | private function remainCount($offset) 71 | { 72 | return $this->nbResults - $offset; 73 | } 74 | 75 | private function createNullArray($length) 76 | { 77 | return array_fill(0, $length, null); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/Propel2Adapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Propel\Runtime\ActiveQuery\ModelCriteria; 15 | 16 | /** 17 | * Propel2Adapter. 18 | * 19 | * @author Raphael YAN 20 | */ 21 | class Propel2Adapter implements AdapterInterface 22 | { 23 | /** 24 | * @var ModelCriteria $query 25 | */ 26 | private $query; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param ModelCriteria $query 32 | */ 33 | public function __construct(ModelCriteria $query) 34 | { 35 | $this->query = $query; 36 | } 37 | 38 | /** 39 | * Returns the query. 40 | * 41 | * @return ModelCriteria 42 | */ 43 | public function getQuery() 44 | { 45 | return $this->query; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getNbResults() 52 | { 53 | $q = clone $this->getQuery(); 54 | 55 | $q->offset(0); 56 | 57 | return $q->count(); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function getSlice($offset, $length) 64 | { 65 | $q = clone $this->getQuery(); 66 | 67 | $q->limit($length); 68 | $q->offset($offset); 69 | 70 | return $q->find(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/PropelAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | /** 15 | * PropelAdapter. 16 | * 17 | * @author William DURAND 18 | */ 19 | class PropelAdapter implements AdapterInterface 20 | { 21 | private $query; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param \ModelCriteria $query 27 | */ 28 | public function __construct($query) 29 | { 30 | $this->query = $query; 31 | } 32 | 33 | /** 34 | * Returns the query. 35 | * 36 | * @return \ModelCriteria 37 | */ 38 | public function getQuery() 39 | { 40 | return $this->query; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getNbResults() 47 | { 48 | $q = clone $this->getQuery(); 49 | 50 | $q->limit(0); 51 | $q->offset(0); 52 | 53 | return $q->count(); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getSlice($offset, $length) 60 | { 61 | $q = clone $this->getQuery(); 62 | 63 | $q->limit($length); 64 | $q->offset($offset); 65 | 66 | return $q->find(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Pagerfanta/Adapter/SolariumAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Adapter; 13 | 14 | use Pagerfanta\Exception\InvalidArgumentException; 15 | use Solarium\QueryType\Select\Query\Query; 16 | use Solarium\Core\Client\Client; 17 | use Solarium_Query_Select; 18 | use Solarium_Client; 19 | 20 | /** 21 | * SolariumAdapter. 22 | * 23 | * @author Igor Wiedler 24 | */ 25 | class SolariumAdapter implements AdapterInterface 26 | { 27 | private $client; 28 | private $query; 29 | private $resultSet; 30 | private $endPoint; 31 | private $resultSetStart; 32 | private $resultSetRows; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param Solarium_Client|Client $client A Solarium client. 38 | * @param Solarium_Query_Select|Query $query A Solarium select query. 39 | */ 40 | public function __construct($client, $query) 41 | { 42 | $this->checkClient($client); 43 | $this->checkQuery($query); 44 | 45 | $this->client = $client; 46 | $this->query = $query; 47 | } 48 | 49 | private function checkClient($client) 50 | { 51 | if ($this->isClientInvalid($client)) { 52 | throw new InvalidArgumentException($this->getClientInvalidMessage($client)); 53 | } 54 | } 55 | 56 | private function isClientInvalid($client) 57 | { 58 | return !($client instanceof Client) && 59 | !($client instanceof Solarium_Client); 60 | } 61 | 62 | private function getClientInvalidMessage($client) 63 | { 64 | return sprintf('The client object should be a Solarium_Client or Solarium\Core\Client\Client instance, %s given', 65 | get_class($client) 66 | ); 67 | } 68 | 69 | private function checkQuery($query) 70 | { 71 | if ($this->isQueryInvalid($query)) { 72 | throw new InvalidArgumentException($this->getQueryInvalidMessage($query)); 73 | } 74 | } 75 | 76 | private function isQueryInvalid($query) 77 | { 78 | return !($query instanceof Query) && 79 | !($query instanceof Solarium_Query_Select); 80 | } 81 | 82 | private function getQueryInvalidMessage($query) 83 | { 84 | return sprintf('The query object should be a Solarium_Query_Select or Solarium\QueryType\Select\Query\Query instance, %s given', 85 | get_class($query) 86 | ); 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function getNbResults() 93 | { 94 | return $this->getResultSet()->getNumFound(); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function getSlice($offset, $length) 101 | { 102 | return $this->getResultSet($offset, $length); 103 | } 104 | 105 | /** 106 | * @param int $start 107 | * @param int $rows 108 | * 109 | * @return \Solarium_Result_Select|\Solarium\QueryType\Select\Result\Result 110 | **/ 111 | public function getResultSet($start = null, $rows = null) 112 | { 113 | if ($this->resultSetStartAndRowsAreNotNullAndChange($start, $rows)) { 114 | $this->resultSetStart = $start; 115 | $this->resultSetRows = $rows; 116 | 117 | $this->modifyQuery(); 118 | $this->clearResultSet(); 119 | } 120 | 121 | if ($this->resultSetEmpty()) { 122 | $this->resultSet = $this->createResultSet(); 123 | } 124 | 125 | return $this->resultSet; 126 | } 127 | 128 | private function resultSetStartAndRowsAreNotNullAndChange($start, $rows) 129 | { 130 | return $this->resultSetStartAndRowsAreNotNull($start, $rows) && 131 | $this->resultSetStartAndRowsChange($start, $rows); 132 | } 133 | 134 | private function resultSetStartAndRowsAreNotNull($start, $rows) 135 | { 136 | return $start !== null && $rows !== null; 137 | } 138 | 139 | private function resultSetStartAndRowsChange($start, $rows) 140 | { 141 | return $start !== $this->resultSetStart || $rows !== $this->resultSetRows; 142 | } 143 | 144 | private function modifyQuery() 145 | { 146 | $this->query 147 | ->setStart($this->resultSetStart) 148 | ->setRows($this->resultSetRows); 149 | } 150 | 151 | private function createResultSet() 152 | { 153 | return $this->client->select($this->query, $this->endPoint); 154 | } 155 | 156 | private function clearResultSet() 157 | { 158 | $this->resultSet = null; 159 | } 160 | 161 | private function resultSetEmpty() 162 | { 163 | return $this->resultSet === null; 164 | } 165 | 166 | public function setEndPoint($endPoint) 167 | { 168 | $this->endPoint = $endPoint; 169 | 170 | return $this; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * Exception. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | interface Exception extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * InvalidArgumentException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class InvalidArgumentException extends \InvalidArgumentException implements Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/LessThan1CurrentPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * LessThan1CurrentPageException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class LessThan1CurrentPageException extends NotValidCurrentPageException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/LessThan1MaxPerPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * LessThan1MaxPerPageException 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class LessThan1MaxPerPageException extends NotValidMaxPerPageException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * LogicException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class LogicException extends \LogicException implements Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotBooleanException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * @author Pablo Díez 16 | */ 17 | class NotBooleanException extends InvalidArgumentException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotIntegerCurrentPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * NotIntegerCurrentPageException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class NotIntegerCurrentPageException extends NotValidCurrentPageException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotIntegerException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * NotIntegerItemException. 16 | * 17 | * @author Alexandre Fayeaux 18 | */ 19 | class NotIntegerException extends InvalidArgumentException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotIntegerMaxPerPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * NotIntegerMaxPerPageException 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class NotIntegerMaxPerPageException extends NotValidMaxPerPageException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotValidCurrentPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * NotValidCurrentPageException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class NotValidCurrentPageException extends InvalidArgumentException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/NotValidMaxPerPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * NotValidMaxPerPageException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class NotValidMaxPerPageException extends InvalidArgumentException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Exception/OutOfRangeCurrentPageException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\Exception; 13 | 14 | /** 15 | * OutOfRangeCurrentPageException. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | class OutOfRangeCurrentPageException extends NotValidCurrentPageException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Pagerfanta/Pagerfanta.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta; 13 | 14 | use OutOfBoundsException; 15 | use Pagerfanta\Adapter\AdapterInterface; 16 | use Pagerfanta\Exception\LogicException; 17 | use Pagerfanta\Exception\NotBooleanException; 18 | use Pagerfanta\Exception\NotIntegerException; 19 | use Pagerfanta\Exception\NotIntegerMaxPerPageException; 20 | use Pagerfanta\Exception\LessThan1MaxPerPageException; 21 | use Pagerfanta\Exception\NotIntegerCurrentPageException; 22 | use Pagerfanta\Exception\LessThan1CurrentPageException; 23 | use Pagerfanta\Exception\OutOfRangeCurrentPageException; 24 | 25 | /** 26 | * Represents a paginator. 27 | * 28 | * @author Pablo Díez 29 | */ 30 | class Pagerfanta implements \Countable, \IteratorAggregate, \JsonSerializable, PagerfantaInterface 31 | { 32 | private $adapter; 33 | private $allowOutOfRangePages; 34 | private $normalizeOutOfRangePages; 35 | private $maxPerPage; 36 | private $currentPage; 37 | private $nbResults; 38 | private $currentPageResults; 39 | 40 | /** 41 | * @param AdapterInterface $adapter An adapter. 42 | */ 43 | public function __construct(AdapterInterface $adapter) 44 | { 45 | $this->adapter = $adapter; 46 | $this->allowOutOfRangePages = false; 47 | $this->normalizeOutOfRangePages = false; 48 | $this->maxPerPage = 10; 49 | $this->currentPage = 1; 50 | } 51 | 52 | /** 53 | * Returns the adapter. 54 | * 55 | * @return AdapterInterface The adapter. 56 | */ 57 | public function getAdapter() 58 | { 59 | return $this->adapter; 60 | } 61 | 62 | /** 63 | * Sets whether or not allow out of range pages. 64 | * 65 | * @param boolean $value 66 | * 67 | * @return self 68 | */ 69 | public function setAllowOutOfRangePages($value) 70 | { 71 | $this->allowOutOfRangePages = $this->filterBoolean($value); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Returns whether or not allow out of range pages. 78 | * 79 | * @return boolean 80 | */ 81 | public function getAllowOutOfRangePages() 82 | { 83 | return $this->allowOutOfRangePages; 84 | } 85 | 86 | /** 87 | * Sets whether or not normalize out of range pages. 88 | * 89 | * @param boolean $value 90 | * 91 | * @return self 92 | */ 93 | public function setNormalizeOutOfRangePages($value) 94 | { 95 | $this->normalizeOutOfRangePages = $this->filterBoolean($value); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Returns whether or not normalize out of range pages. 102 | * 103 | * @return boolean 104 | */ 105 | public function getNormalizeOutOfRangePages() 106 | { 107 | return $this->normalizeOutOfRangePages; 108 | } 109 | 110 | private function filterBoolean($value) 111 | { 112 | if (!is_bool($value)) { 113 | throw new NotBooleanException(); 114 | } 115 | 116 | return $value; 117 | } 118 | 119 | /** 120 | * Sets the max per page. 121 | * 122 | * Tries to convert from string and float. 123 | * 124 | * @param integer $maxPerPage 125 | * 126 | * @return self 127 | * 128 | * @throws NotIntegerMaxPerPageException If the max per page is not an integer even converting. 129 | * @throws LessThan1MaxPerPageException If the max per page is less than 1. 130 | */ 131 | public function setMaxPerPage($maxPerPage) 132 | { 133 | $this->maxPerPage = $this->filterMaxPerPage($maxPerPage); 134 | $this->resetForMaxPerPageChange(); 135 | 136 | return $this; 137 | } 138 | 139 | private function filterMaxPerPage($maxPerPage) 140 | { 141 | $maxPerPage = $this->toInteger($maxPerPage); 142 | $this->checkMaxPerPage($maxPerPage); 143 | 144 | return $maxPerPage; 145 | } 146 | 147 | private function checkMaxPerPage($maxPerPage) 148 | { 149 | if (!is_int($maxPerPage)) { 150 | throw new NotIntegerMaxPerPageException(); 151 | } 152 | 153 | if ($maxPerPage < 1) { 154 | throw new LessThan1MaxPerPageException(); 155 | } 156 | } 157 | 158 | private function resetForMaxPerPageChange() 159 | { 160 | $this->currentPageResults = null; 161 | $this->nbResults = null; 162 | } 163 | 164 | /** 165 | * Returns the max per page. 166 | * 167 | * @return integer 168 | */ 169 | public function getMaxPerPage() 170 | { 171 | return $this->maxPerPage; 172 | } 173 | 174 | /** 175 | * Sets the current page. 176 | * 177 | * Tries to convert from string and float. 178 | * 179 | * @param integer $currentPage 180 | * 181 | * @return self 182 | * 183 | * @throws NotIntegerCurrentPageException If the current page is not an integer even converting. 184 | * @throws LessThan1CurrentPageException If the current page is less than 1. 185 | * @throws OutOfRangeCurrentPageException If It is not allowed out of range pages and they are not normalized. 186 | */ 187 | public function setCurrentPage($currentPage) 188 | { 189 | $this->useDeprecatedCurrentPageBooleanArguments(func_get_args()); 190 | 191 | $this->currentPage = $this->filterCurrentPage($currentPage); 192 | $this->resetForCurrentPageChange(); 193 | 194 | return $this; 195 | } 196 | 197 | private function useDeprecatedCurrentPageBooleanArguments($arguments) 198 | { 199 | $this->useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument($arguments); 200 | $this->useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument($arguments); 201 | } 202 | 203 | private function useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument($arguments) 204 | { 205 | $index = 1; 206 | $method = 'setAllowOutOfRangePages'; 207 | 208 | $this->useDeprecatedBooleanArgument($arguments, $index, $method); 209 | } 210 | 211 | private function useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument($arguments) 212 | { 213 | $index = 2; 214 | $method = 'setNormalizeOutOfRangePages'; 215 | 216 | $this->useDeprecatedBooleanArgument($arguments, $index, $method); 217 | } 218 | 219 | private function useDeprecatedBooleanArgument($arguments, $index, $method) 220 | { 221 | if (isset($arguments[$index])) { 222 | $this->$method($arguments[$index]); 223 | } 224 | } 225 | 226 | private function filterCurrentPage($currentPage) 227 | { 228 | $currentPage = $this->toInteger($currentPage); 229 | $this->checkCurrentPage($currentPage); 230 | $currentPage = $this->filterOutOfRangeCurrentPage($currentPage); 231 | 232 | return $currentPage; 233 | } 234 | 235 | private function checkCurrentPage($currentPage) 236 | { 237 | if (!is_int($currentPage)) { 238 | throw new NotIntegerCurrentPageException(); 239 | } 240 | 241 | if ($currentPage < 1) { 242 | throw new LessThan1CurrentPageException(); 243 | } 244 | } 245 | 246 | private function filterOutOfRangeCurrentPage($currentPage) 247 | { 248 | if ($this->notAllowedCurrentPageOutOfRange($currentPage)) { 249 | return $this->normalizeOutOfRangeCurrentPage($currentPage); 250 | } 251 | 252 | return $currentPage; 253 | } 254 | 255 | private function notAllowedCurrentPageOutOfRange($currentPage) 256 | { 257 | return !$this->getAllowOutOfRangePages() && 258 | $this->currentPageOutOfRange($currentPage); 259 | } 260 | 261 | private function currentPageOutOfRange($currentPage) 262 | { 263 | return $currentPage > 1 && $currentPage > $this->getNbPages(); 264 | } 265 | 266 | /** 267 | * @param int $currentPage 268 | * 269 | * @return int 270 | * 271 | * @throws OutOfRangeCurrentPageException If the page should not be normalized 272 | */ 273 | private function normalizeOutOfRangeCurrentPage($currentPage) 274 | { 275 | if ($this->getNormalizeOutOfRangePages()) { 276 | return $this->getNbPages(); 277 | } 278 | 279 | throw new OutOfRangeCurrentPageException(sprintf('Page "%d" does not exist. The currentPage must be inferior to "%d"', $currentPage, $this->getNbPages())); 280 | } 281 | 282 | private function resetForCurrentPageChange() 283 | { 284 | $this->currentPageResults = null; 285 | } 286 | 287 | /** 288 | * Returns the current page. 289 | * 290 | * @return integer 291 | */ 292 | public function getCurrentPage() 293 | { 294 | return $this->currentPage; 295 | } 296 | 297 | /** 298 | * Returns the results for the current page. 299 | * 300 | * @return array|\Traversable 301 | */ 302 | public function getCurrentPageResults() 303 | { 304 | if ($this->notCachedCurrentPageResults()) { 305 | $this->currentPageResults = $this->getCurrentPageResultsFromAdapter(); 306 | } 307 | 308 | return $this->currentPageResults; 309 | } 310 | 311 | private function notCachedCurrentPageResults() 312 | { 313 | return $this->currentPageResults === null; 314 | } 315 | 316 | private function getCurrentPageResultsFromAdapter() 317 | { 318 | $offset = $this->calculateOffsetForCurrentPageResults(); 319 | $length = $this->getMaxPerPage(); 320 | 321 | return $this->adapter->getSlice($offset, $length); 322 | } 323 | 324 | private function calculateOffsetForCurrentPageResults() 325 | { 326 | return ($this->getCurrentPage() - 1) * $this->getMaxPerPage(); 327 | } 328 | 329 | /** 330 | * Calculates the current page offset start 331 | * 332 | * @return int 333 | */ 334 | public function getCurrentPageOffsetStart() 335 | { 336 | return $this->getNbResults() ? 337 | $this->calculateOffsetForCurrentPageResults() + 1 : 338 | 0; 339 | } 340 | 341 | /** 342 | * Calculates the current page offset end 343 | * 344 | * @return int 345 | */ 346 | public function getCurrentPageOffsetEnd() 347 | { 348 | return $this->hasNextPage() ? 349 | $this->getCurrentPage() * $this->getMaxPerPage() : 350 | $this->getNbResults(); 351 | } 352 | 353 | /** 354 | * Returns the number of results. 355 | * 356 | * @return integer 357 | */ 358 | public function getNbResults() 359 | { 360 | if ($this->notCachedNbResults()) { 361 | $this->nbResults = $this->getAdapter()->getNbResults(); 362 | } 363 | 364 | return $this->nbResults; 365 | } 366 | 367 | private function notCachedNbResults() 368 | { 369 | return $this->nbResults === null; 370 | } 371 | 372 | /** 373 | * Returns the number of pages. 374 | * 375 | * @return integer 376 | */ 377 | public function getNbPages() 378 | { 379 | $nbPages = $this->calculateNbPages(); 380 | 381 | if ($nbPages == 0) { 382 | return $this->minimumNbPages(); 383 | } 384 | 385 | return $nbPages; 386 | } 387 | 388 | private function calculateNbPages() 389 | { 390 | return (int) ceil($this->getNbResults() / $this->getMaxPerPage()); 391 | } 392 | 393 | private function minimumNbPages() 394 | { 395 | return 1; 396 | } 397 | 398 | /** 399 | * Returns if the number of results is higher than the max per page. 400 | * 401 | * @return boolean 402 | */ 403 | public function haveToPaginate() 404 | { 405 | return $this->getNbResults() > $this->maxPerPage; 406 | } 407 | 408 | /** 409 | * Returns whether there is previous page or not. 410 | * 411 | * @return boolean 412 | */ 413 | public function hasPreviousPage() 414 | { 415 | return $this->currentPage > 1; 416 | } 417 | 418 | /** 419 | * Returns the previous page. 420 | * 421 | * @return integer 422 | * 423 | * @throws LogicException If there is no previous page. 424 | */ 425 | public function getPreviousPage() 426 | { 427 | if (!$this->hasPreviousPage()) { 428 | throw new LogicException('There is no previous page.'); 429 | } 430 | 431 | return $this->currentPage - 1; 432 | } 433 | 434 | /** 435 | * Returns whether there is next page or not. 436 | * 437 | * @return boolean 438 | */ 439 | public function hasNextPage() 440 | { 441 | return $this->currentPage < $this->getNbPages(); 442 | } 443 | 444 | /** 445 | * Returns the next page. 446 | * 447 | * @return integer 448 | * 449 | * @throws LogicException If there is no next page. 450 | */ 451 | public function getNextPage() 452 | { 453 | if (!$this->hasNextPage()) { 454 | throw new LogicException('There is no next page.'); 455 | } 456 | 457 | return $this->currentPage + 1; 458 | } 459 | 460 | /** 461 | * Implements the \Countable interface. 462 | * 463 | * @return integer The number of results. 464 | */ 465 | public function count() 466 | { 467 | return $this->getNbResults(); 468 | } 469 | 470 | /** 471 | * Implements the \IteratorAggregate interface. 472 | * 473 | * @return \ArrayIterator instance with the current results. 474 | */ 475 | public function getIterator() 476 | { 477 | $results = $this->getCurrentPageResults(); 478 | 479 | if ($results instanceof \Iterator) { 480 | return $results; 481 | } 482 | 483 | if ($results instanceof \IteratorAggregate) { 484 | return $results->getIterator(); 485 | } 486 | 487 | return new \ArrayIterator($results); 488 | } 489 | 490 | /** 491 | * Implements the \JsonSerializable interface. 492 | * 493 | * @return array current page results 494 | */ 495 | public function jsonSerialize() 496 | { 497 | $results = $this->getCurrentPageResults(); 498 | if ($results instanceof \Traversable) { 499 | return iterator_to_array($results); 500 | } 501 | 502 | return $results; 503 | } 504 | 505 | private function toInteger($value) 506 | { 507 | if ($this->needsToIntegerConversion($value)) { 508 | return (int) $value; 509 | } 510 | 511 | return $value; 512 | } 513 | 514 | private function needsToIntegerConversion($value) 515 | { 516 | return (is_string($value) || is_float($value)) && (int) $value == $value; 517 | } 518 | 519 | /** 520 | * Get page number of the item at specified position (1-based index) 521 | * 522 | * @param integer $position 523 | * 524 | * @return integer 525 | */ 526 | public function getPageNumberForItemAtPosition($position) 527 | { 528 | if (!is_int($position)) { 529 | throw new NotIntegerException(); 530 | } 531 | 532 | if ($this->getNbResults() < $position) { 533 | throw new OutOfBoundsException(sprintf( 534 | 'Item requested at position %d, but there are only %d items.', 535 | $position, 536 | $this->getNbResults() 537 | )); 538 | } 539 | 540 | return (int) ceil($position/$this->getMaxPerPage()); 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /src/Pagerfanta/PagerfantaInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta; 13 | 14 | /** 15 | * @deprecated 16 | */ 17 | interface PagerfantaInterface 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/DefaultView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\PagerfantaInterface; 15 | use Pagerfanta\View\Template\TemplateInterface; 16 | use Pagerfanta\View\Template\DefaultTemplate; 17 | 18 | /** 19 | * @author Pablo Díez 20 | */ 21 | class DefaultView implements ViewInterface 22 | { 23 | private $template; 24 | 25 | private $pagerfanta; 26 | private $proximity; 27 | 28 | private $currentPage; 29 | private $nbPages; 30 | 31 | private $startPage; 32 | private $endPage; 33 | 34 | public function __construct(TemplateInterface $template = null) 35 | { 36 | $this->template = $template ?: $this->createDefaultTemplate(); 37 | } 38 | 39 | protected function createDefaultTemplate() 40 | { 41 | return new DefaultTemplate(); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function render(PagerfantaInterface $pagerfanta, $routeGenerator, array $options = array()) 48 | { 49 | $this->initializePagerfanta($pagerfanta); 50 | $this->initializeOptions($options); 51 | 52 | $this->configureTemplate($routeGenerator, $options); 53 | 54 | return $this->generate(); 55 | } 56 | 57 | private function initializePagerfanta(PagerfantaInterface $pagerfanta) 58 | { 59 | $this->pagerfanta = $pagerfanta; 60 | 61 | $this->currentPage = $pagerfanta->getCurrentPage(); 62 | $this->nbPages = $pagerfanta->getNbPages(); 63 | } 64 | 65 | private function initializeOptions($options) 66 | { 67 | $this->proximity = isset($options['proximity']) ? 68 | (int) $options['proximity'] : 69 | $this->getDefaultProximity(); 70 | } 71 | 72 | protected function getDefaultProximity() 73 | { 74 | return 2; 75 | } 76 | 77 | private function configureTemplate($routeGenerator, $options) 78 | { 79 | $this->template->setRouteGenerator($routeGenerator); 80 | $this->template->setOptions($options); 81 | } 82 | 83 | private function generate() 84 | { 85 | $pages = $this->generatePages(); 86 | 87 | return $this->generateContainer($pages); 88 | } 89 | 90 | private function generateContainer($pages) 91 | { 92 | return str_replace('%pages%', $pages, $this->template->container()); 93 | } 94 | 95 | private function generatePages() 96 | { 97 | $this->calculateStartAndEndPage(); 98 | 99 | return $this->previous(). 100 | $this->first(). 101 | $this->secondIfStartIs3(). 102 | $this->dotsIfStartIsOver3(). 103 | $this->pages(). 104 | $this->dotsIfEndIsUnder3ToLast(). 105 | $this->secondToLastIfEndIs3ToLast(). 106 | $this->last(). 107 | $this->next(); 108 | } 109 | 110 | private function calculateStartAndEndPage() 111 | { 112 | $startPage = $this->currentPage - $this->proximity; 113 | $endPage = $this->currentPage + $this->proximity; 114 | 115 | if ($this->startPageUnderflow($startPage)) { 116 | $endPage = $this->calculateEndPageForStartPageUnderflow($startPage, $endPage); 117 | $startPage = 1; 118 | } 119 | if ($this->endPageOverflow($endPage)) { 120 | $startPage = $this->calculateStartPageForEndPageOverflow($startPage, $endPage); 121 | $endPage = $this->nbPages; 122 | } 123 | 124 | $this->startPage = $startPage; 125 | $this->endPage = $endPage; 126 | } 127 | 128 | private function startPageUnderflow($startPage) 129 | { 130 | return $startPage < 1; 131 | } 132 | 133 | private function endPageOverflow($endPage) 134 | { 135 | return $endPage > $this->nbPages; 136 | } 137 | 138 | private function calculateEndPageForStartPageUnderflow($startPage, $endPage) 139 | { 140 | return min($endPage + (1 - $startPage), $this->nbPages); 141 | } 142 | 143 | private function calculateStartPageForEndPageOverflow($startPage, $endPage) 144 | { 145 | return max($startPage - ($endPage - $this->nbPages), 1); 146 | } 147 | 148 | private function previous() 149 | { 150 | if ($this->pagerfanta->hasPreviousPage()) { 151 | return $this->template->previousEnabled($this->pagerfanta->getPreviousPage()); 152 | } 153 | 154 | return $this->template->previousDisabled(); 155 | } 156 | 157 | private function first() 158 | { 159 | if ($this->startPage > 1) { 160 | return $this->template->first(); 161 | } 162 | } 163 | 164 | private function secondIfStartIs3() 165 | { 166 | if ($this->startPage == 3) { 167 | return $this->template->page(2); 168 | } 169 | } 170 | 171 | private function dotsIfStartIsOver3() 172 | { 173 | if ($this->startPage > 3) { 174 | return $this->template->separator(); 175 | } 176 | } 177 | 178 | private function pages() 179 | { 180 | $pages = ''; 181 | 182 | foreach (range($this->startPage, $this->endPage) as $page) { 183 | $pages .= $this->page($page); 184 | } 185 | 186 | return $pages; 187 | } 188 | 189 | private function page($page) 190 | { 191 | if ($page == $this->currentPage) { 192 | return $this->template->current($page); 193 | } 194 | 195 | return $this->template->page($page); 196 | } 197 | 198 | private function dotsIfEndIsUnder3ToLast() 199 | { 200 | if ($this->endPage < $this->toLast(3)) { 201 | return $this->template->separator(); 202 | } 203 | } 204 | 205 | private function secondToLastIfEndIs3ToLast() 206 | { 207 | if ($this->endPage == $this->toLast(3)) { 208 | return $this->template->page($this->toLast(2)); 209 | } 210 | } 211 | 212 | private function toLast($n) 213 | { 214 | return $this->pagerfanta->getNbPages() - ($n - 1); 215 | } 216 | 217 | private function last() 218 | { 219 | if ($this->pagerfanta->getNbPages() > $this->endPage) { 220 | return $this->template->last($this->pagerfanta->getNbPages()); 221 | } 222 | } 223 | 224 | private function next() 225 | { 226 | if ($this->pagerfanta->hasNextPage()) { 227 | return $this->template->nextEnabled($this->pagerfanta->getNextPage()); 228 | } 229 | 230 | return $this->template->nextDisabled(); 231 | } 232 | 233 | /** 234 | * {@inheritdoc} 235 | */ 236 | public function getName() 237 | { 238 | return 'default'; 239 | } 240 | } 241 | 242 | /* 243 | 244 | CSS: 245 | 246 | .pagerfanta { 247 | } 248 | 249 | .pagerfanta a, 250 | .pagerfanta span { 251 | display: inline-block; 252 | border: 1px solid blue; 253 | color: blue; 254 | margin-right: .2em; 255 | padding: .25em .35em; 256 | } 257 | 258 | .pagerfanta a { 259 | text-decoration: none; 260 | } 261 | 262 | .pagerfanta a:hover { 263 | background: #ccf; 264 | } 265 | 266 | .pagerfanta .dots { 267 | border-width: 0; 268 | } 269 | 270 | .pagerfanta .current { 271 | background: #ccf; 272 | font-weight: bold; 273 | } 274 | 275 | .pagerfanta .disabled { 276 | border-color: #ccf; 277 | color: #ccf; 278 | } 279 | 280 | COLORS: 281 | 282 | .pagerfanta a, 283 | .pagerfanta span { 284 | border-color: blue; 285 | color: blue; 286 | } 287 | 288 | .pagerfanta a:hover { 289 | background: #ccf; 290 | } 291 | 292 | .pagerfanta .current { 293 | background: #ccf; 294 | } 295 | 296 | .pagerfanta .disabled { 297 | border-color: #ccf; 298 | color: #cf; 299 | } 300 | 301 | */ 302 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/OptionableView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\PagerfantaInterface; 15 | 16 | /** 17 | * OptionableView. 18 | * 19 | * This view renders another view with a default options to reuse them in a project. 20 | * 21 | * @author Pablo Díez 22 | */ 23 | class OptionableView implements ViewInterface 24 | { 25 | private $view; 26 | private $defaultOptions; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param ViewInterface $view A view. 32 | * @param array $defaultOptions An array of default options. 33 | */ 34 | public function __construct(ViewInterface $view, array $defaultOptions) 35 | { 36 | $this->view = $view; 37 | $this->defaultOptions = $defaultOptions; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function render(PagerfantaInterface $pagerfanta, $routeGenerator, array $options = array()) 44 | { 45 | return $this->view->render($pagerfanta, $routeGenerator, array_merge($this->defaultOptions, $options)); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getName() 52 | { 53 | return 'optionable'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/SemanticUiView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\View\Template\SemanticUiTemplate; 15 | 16 | /** 17 | * SemanticUiView. 18 | * 19 | * View that can be used with the pagination module 20 | * from the Semantic UI CSS Toolkit 21 | * http://semantic-ui.com/ 22 | * 23 | * @author Loïc Frémont 24 | */ 25 | class SemanticUiView extends DefaultView 26 | { 27 | protected function createDefaultTemplate() 28 | { 29 | return new SemanticUiTemplate(); 30 | } 31 | 32 | protected function getDefaultProximity() 33 | { 34 | return 3; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function getName() 41 | { 42 | return 'semantic_ui'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/DefaultTemplate.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * @author Pablo Díez 16 | */ 17 | class DefaultTemplate extends Template 18 | { 19 | static protected $defaultOptions = array( 20 | 'prev_message' => 'Previous', 21 | 'next_message' => 'Next', 22 | 'css_disabled_class' => 'disabled', 23 | 'css_dots_class' => 'dots', 24 | 'css_current_class' => 'current', 25 | 'dots_text' => '...', 26 | 'container_template' => '', 27 | 'page_template' => '%text%', 28 | 'span_template' => '%text%', 29 | 'rel_previous' => 'prev', 30 | 'rel_next' => 'next' 31 | ); 32 | 33 | public function container() 34 | { 35 | return $this->option('container_template'); 36 | } 37 | 38 | public function page($page) 39 | { 40 | $text = $page; 41 | 42 | return $this->pageWithText($page, $text); 43 | } 44 | 45 | public function pageWithText($page, $text, $rel = null) 46 | { 47 | $search = array('%href%', '%text%', '%rel%'); 48 | 49 | $href = $this->generateRoute($page); 50 | $replace = $rel ? array($href, $text, ' rel="' . $rel . '"') : array($href, $text, ''); 51 | 52 | return str_replace($search, $replace, $this->option('page_template')); 53 | } 54 | 55 | public function previousDisabled() 56 | { 57 | return $this->generateSpan($this->option('css_disabled_class'), $this->option('prev_message')); 58 | } 59 | 60 | public function previousEnabled($page) 61 | { 62 | return $this->pageWithText($page, $this->option('prev_message'), $this->option('rel_previous')); 63 | } 64 | 65 | public function nextDisabled() 66 | { 67 | return $this->generateSpan($this->option('css_disabled_class'), $this->option('next_message')); 68 | } 69 | 70 | public function nextEnabled($page) 71 | { 72 | return $this->pageWithText($page, $this->option('next_message'), $this->option('rel_next')); 73 | } 74 | 75 | public function first() 76 | { 77 | return $this->page(1); 78 | } 79 | 80 | public function last($page) 81 | { 82 | return $this->page($page); 83 | } 84 | 85 | public function current($page) 86 | { 87 | return $this->generateSpan($this->option('css_current_class'), $page); 88 | } 89 | 90 | public function separator() 91 | { 92 | return $this->generateSpan($this->option('css_dots_class'), $this->option('dots_text')); 93 | } 94 | 95 | private function generateSpan($class, $page) 96 | { 97 | $search = array('%class%', '%text%'); 98 | $replace = array($class, $page); 99 | 100 | return str_replace($search, $replace, $this->option('span_template')); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/SemanticUiTemplate.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * @author Loïc Frémont 16 | */ 17 | class SemanticUiTemplate extends Template 18 | { 19 | static protected $defaultOptions = array( 20 | 'prev_message' => '← Previous', 21 | 'next_message' => 'Next →', 22 | 'dots_message' => '…', 23 | 'active_suffix' => '', 24 | 'css_container_class' => 'ui stackable fluid pagination menu', 25 | 'css_item_class' => 'item', 26 | 'css_prev_class' => 'prev', 27 | 'css_next_class' => 'next', 28 | 'css_disabled_class' => 'disabled', 29 | 'css_dots_class' => 'disabled', 30 | 'css_active_class' => 'active', 31 | ); 32 | 33 | public function container() 34 | { 35 | return sprintf('
%%pages%%
', 36 | $this->option('css_container_class') 37 | ); 38 | } 39 | 40 | public function page($page) 41 | { 42 | $text = $page; 43 | 44 | return $this->pageWithText($page, $text); 45 | } 46 | 47 | public function pageWithText($page, $text) 48 | { 49 | $class = null; 50 | 51 | return $this->pageWithTextAndClass($page, $text, $class); 52 | } 53 | 54 | private function pageWithTextAndClass($page, $text, $class) 55 | { 56 | $href = $this->generateRoute($page); 57 | 58 | return $this->link($class, $href, $text); 59 | } 60 | 61 | public function previousDisabled() 62 | { 63 | $class = $this->previousDisabledClass(); 64 | $text = $this->option('prev_message'); 65 | 66 | return $this->div($class, $text); 67 | } 68 | 69 | private function previousDisabledClass() 70 | { 71 | return $this->option('css_prev_class').' '.$this->option('css_disabled_class'); 72 | } 73 | 74 | public function previousEnabled($page) 75 | { 76 | $text = $this->option('prev_message'); 77 | $class = $this->option('css_prev_class'); 78 | 79 | return $this->pageWithTextAndClass($page, $text, $class); 80 | } 81 | 82 | public function nextDisabled() 83 | { 84 | $class = $this->nextDisabledClass(); 85 | $text = $this->option('next_message'); 86 | 87 | return $this->div($class, $text); 88 | } 89 | 90 | private function nextDisabledClass() 91 | { 92 | return $this->option('css_next_class').' '.$this->option('css_disabled_class'); 93 | } 94 | 95 | public function nextEnabled($page) 96 | { 97 | $text = $this->option('next_message'); 98 | $class = $this->option('css_next_class'); 99 | 100 | return $this->pageWithTextAndClass($page, $text, $class); 101 | } 102 | 103 | public function first() 104 | { 105 | return $this->page(1); 106 | } 107 | 108 | public function last($page) 109 | { 110 | return $this->page($page); 111 | } 112 | 113 | public function current($page) 114 | { 115 | $text = trim($page.' '.$this->option('active_suffix')); 116 | $class = $this->option('css_active_class'); 117 | 118 | return $this->div($class, $text); 119 | } 120 | 121 | public function separator() 122 | { 123 | $class = $this->option('css_dots_class'); 124 | $text = $this->option('dots_message'); 125 | 126 | return $this->div($class, $text); 127 | } 128 | 129 | private function link($class, $href, $text) 130 | { 131 | $item_class = $this->option('css_item_class'); 132 | 133 | return sprintf('%s', $item_class, $class, $href, $text); 134 | } 135 | 136 | private function div($class, $text) 137 | { 138 | $item_class = $this->option('css_item_class'); 139 | 140 | return sprintf('
%s
', $item_class, $class, $text); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/Template.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * @author Pablo Díez 16 | */ 17 | abstract class Template implements TemplateInterface 18 | { 19 | protected static $defaultOptions = array(); 20 | 21 | private $routeGenerator; 22 | private $options; 23 | 24 | public function __construct() 25 | { 26 | $this->initializeOptions(); 27 | } 28 | 29 | public function setRouteGenerator($routeGenerator) 30 | { 31 | $this->routeGenerator = $routeGenerator; 32 | } 33 | 34 | public function setOptions(array $options) 35 | { 36 | $this->options = array_merge($this->options, $options); 37 | } 38 | 39 | private function initializeOptions() 40 | { 41 | $this->options = static::$defaultOptions; 42 | } 43 | 44 | protected function generateRoute($page) 45 | { 46 | return call_user_func($this->getRouteGenerator(), $page); 47 | } 48 | 49 | private function getRouteGenerator() 50 | { 51 | if (!$this->routeGenerator) { 52 | throw new \RuntimeException('There is no route generator.'); 53 | } 54 | 55 | return $this->routeGenerator; 56 | } 57 | 58 | protected function option($name) 59 | { 60 | if (!isset($this->options[$name])) { 61 | throw new \InvalidArgumentException(sprintf('The option "%s" does not exist.', $name)); 62 | } 63 | 64 | return $this->options[$name]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/TemplateInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * @author Pablo Díez 16 | */ 17 | interface TemplateInterface 18 | { 19 | /** 20 | * Renders the container for the pagination. 21 | * 22 | * The %pages% placeholder will be replaced by the rendering of pages 23 | * 24 | * @return string 25 | */ 26 | public function container(); 27 | 28 | /** 29 | * Renders a given page. 30 | * 31 | * @param int $page 32 | * 33 | * @return string 34 | */ 35 | public function page($page); 36 | 37 | /** 38 | * Renders a given page with a specified text. 39 | * 40 | * @param int $page 41 | * @param string $text 42 | * 43 | * @return string 44 | */ 45 | public function pageWithText($page, $text); 46 | 47 | /** 48 | * Renders the disabled state of the previous page. 49 | * 50 | * @return string 51 | */ 52 | public function previousDisabled(); 53 | 54 | /** 55 | * Renders the enabled state of the previous page. 56 | * 57 | * @param int $page 58 | * 59 | * @return string 60 | */ 61 | public function previousEnabled($page); 62 | 63 | /** 64 | * Renders the disabled state of the next page. 65 | * 66 | * @return string 67 | */ 68 | public function nextDisabled(); 69 | 70 | /** 71 | * Renders the enabled state of the next page. 72 | * 73 | * @param int $page 74 | * 75 | * @return string 76 | */ 77 | public function nextEnabled($page); 78 | 79 | /** 80 | * Renders the first page. 81 | * 82 | * @return string 83 | */ 84 | public function first(); 85 | 86 | /** 87 | * Renders the last page. 88 | * 89 | * @param int $page 90 | * 91 | * @return string 92 | */ 93 | public function last($page); 94 | 95 | /** 96 | * Renders the current page. 97 | * 98 | * @param int $page 99 | * 100 | * @return string 101 | */ 102 | public function current($page); 103 | 104 | /** 105 | * Renders the separator between pages. 106 | * 107 | * @return string 108 | */ 109 | public function separator(); 110 | } 111 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/TwitterBootstrap3Template.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * TwitterBootstrap3Template 16 | */ 17 | class TwitterBootstrap3Template extends TwitterBootstrapTemplate 18 | { 19 | public function __construct() 20 | { 21 | parent::__construct(); 22 | 23 | $this->setOptions(array('active_suffix' => '(current)')); 24 | } 25 | 26 | public function container() 27 | { 28 | return sprintf('
    %%pages%%
', 29 | $this->option('css_container_class') 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/TwitterBootstrap4Template.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * TwitterBootstrap4Template 16 | */ 17 | class TwitterBootstrap4Template extends TwitterBootstrap3Template 18 | { 19 | protected function linkLi($class, $href, $text, $rel = null) 20 | { 21 | $liClass = implode(' ', array_filter(array('page-item', $class))); 22 | $rel = $rel ? sprintf(' rel="%s"', $rel) : ''; 23 | 24 | return sprintf('
  • %s
  • ', $liClass, $href, $rel, $text); 25 | } 26 | 27 | protected function spanLi($class, $text) 28 | { 29 | $liClass = implode(' ', array_filter(array('page-item', $class))); 30 | 31 | return sprintf('
  • %s
  • ', $liClass, $text); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/Template/TwitterBootstrapTemplate.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View\Template; 13 | 14 | /** 15 | * @author Pablo Díez 16 | */ 17 | class TwitterBootstrapTemplate extends Template 18 | { 19 | static protected $defaultOptions = array( 20 | 'prev_message' => '← Previous', 21 | 'next_message' => 'Next →', 22 | 'dots_message' => '…', 23 | 'active_suffix' => '', 24 | 'css_container_class' => 'pagination', 25 | 'css_prev_class' => 'prev', 26 | 'css_next_class' => 'next', 27 | 'css_disabled_class' => 'disabled', 28 | 'css_dots_class' => 'disabled', 29 | 'css_active_class' => 'active', 30 | 'rel_previous' => 'prev', 31 | 'rel_next' => 'next' 32 | ); 33 | 34 | public function container() 35 | { 36 | return sprintf('
      %%pages%%
    ', 37 | $this->option('css_container_class') 38 | ); 39 | } 40 | 41 | public function page($page) 42 | { 43 | $text = $page; 44 | 45 | return $this->pageWithText($page, $text); 46 | } 47 | 48 | public function pageWithText($page, $text) 49 | { 50 | $class = null; 51 | 52 | return $this->pageWithTextAndClass($page, $text, $class); 53 | } 54 | 55 | private function pageWithTextAndClass($page, $text, $class, $rel = null) 56 | { 57 | $href = $this->generateRoute($page); 58 | 59 | return $this->linkLi($class, $href, $text, $rel); 60 | } 61 | 62 | public function previousDisabled() 63 | { 64 | $class = $this->previousDisabledClass(); 65 | $text = $this->option('prev_message'); 66 | 67 | return $this->spanLi($class, $text); 68 | } 69 | 70 | private function previousDisabledClass() 71 | { 72 | return $this->option('css_prev_class').' '.$this->option('css_disabled_class'); 73 | } 74 | 75 | public function previousEnabled($page) 76 | { 77 | $text = $this->option('prev_message'); 78 | $class = $this->option('css_prev_class'); 79 | $rel = $this->option('rel_previous'); 80 | 81 | return $this->pageWithTextAndClass($page, $text, $class, $rel); 82 | } 83 | 84 | public function nextDisabled() 85 | { 86 | $class = $this->nextDisabledClass(); 87 | $text = $this->option('next_message'); 88 | 89 | return $this->spanLi($class, $text); 90 | } 91 | 92 | private function nextDisabledClass() 93 | { 94 | return $this->option('css_next_class').' '.$this->option('css_disabled_class'); 95 | } 96 | 97 | public function nextEnabled($page) 98 | { 99 | $text = $this->option('next_message'); 100 | $class = $this->option('css_next_class'); 101 | $rel = $this->option('rel_next'); 102 | 103 | return $this->pageWithTextAndClass($page, $text, $class, $rel); 104 | } 105 | 106 | public function first() 107 | { 108 | return $this->page(1); 109 | } 110 | 111 | public function last($page) 112 | { 113 | return $this->page($page); 114 | } 115 | 116 | public function current($page) 117 | { 118 | $text = trim($page.' '.$this->option('active_suffix')); 119 | $class = $this->option('css_active_class'); 120 | 121 | return $this->spanLi($class, $text); 122 | } 123 | 124 | public function separator() 125 | { 126 | $class = $this->option('css_dots_class'); 127 | $text = $this->option('dots_message'); 128 | 129 | return $this->spanLi($class, $text); 130 | } 131 | 132 | protected function linkLi($class, $href, $text, $rel = null) 133 | { 134 | $liClass = $class ? sprintf(' class="%s"', $class) : ''; 135 | $rel = $rel ? sprintf(' rel="%s"', $rel) : ''; 136 | 137 | return sprintf('%s', $liClass, $href, $rel, $text); 138 | } 139 | 140 | protected function spanLi($class, $text) 141 | { 142 | $liClass = $class ? sprintf(' class="%s"', $class) : ''; 143 | 144 | return sprintf('%s', $liClass, $text); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/TwitterBootstrap3View.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\View\Template\TwitterBootstrap3Template; 15 | 16 | /** 17 | * TwitterBootstrap3View. 18 | * 19 | * View that can be used with the pagination module 20 | * from the Twitter Bootstrap3 CSS Toolkit 21 | * http://getbootstrap.com/ 22 | * 23 | */ 24 | class TwitterBootstrap3View extends TwitterBootstrapView 25 | { 26 | protected function createDefaultTemplate() 27 | { 28 | return new TwitterBootstrap3Template(); 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function getName() 35 | { 36 | return 'twitter_bootstrap3'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/TwitterBootstrap4View.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\View\Template\TwitterBootstrap4Template; 15 | 16 | /** 17 | * TwitterBootstrap4View. 18 | * 19 | * View that can be used with the pagination module 20 | * from the Twitter Bootstrap4 CSS Toolkit 21 | * http://getbootstrap.com/ 22 | * 23 | */ 24 | class TwitterBootstrap4View extends TwitterBootstrapView 25 | { 26 | protected function createDefaultTemplate() 27 | { 28 | return new TwitterBootstrap4Template(); 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function getName() 35 | { 36 | return 'twitter_bootstrap4'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/TwitterBootstrapView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\View\Template\TwitterBootstrapTemplate; 15 | 16 | /** 17 | * TwitterBootstrapView. 18 | * 19 | * View that can be used with the pagination module 20 | * from the Twitter Bootstrap CSS Toolkit 21 | * http://twitter.github.com/bootstrap/ 22 | * 23 | * @author Pablo Díez 24 | * @author Jan Sorgalla 25 | */ 26 | class TwitterBootstrapView extends DefaultView 27 | { 28 | protected function createDefaultTemplate() 29 | { 30 | return new TwitterBootstrapTemplate(); 31 | } 32 | 33 | protected function getDefaultProximity() 34 | { 35 | return 3; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getName() 42 | { 43 | return 'twitter_bootstrap'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/ViewFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * ViewFactory. 18 | * 19 | * @author Pablo Díez 20 | */ 21 | class ViewFactory implements ViewFactoryInterface 22 | { 23 | private $views; 24 | 25 | /** 26 | * Constructor. 27 | */ 28 | public function __construct() 29 | { 30 | $this->views = array(); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function set($name, ViewInterface $view) 37 | { 38 | $this->views[$name] = $view; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function has($name) 45 | { 46 | return isset($this->views[$name]); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function add(array $views) 53 | { 54 | foreach ($views as $name => $view) { 55 | $this->set($name, $view); 56 | } 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function get($name) 63 | { 64 | if (!$this->has($name)) { 65 | throw new InvalidArgumentException(sprintf('The view "%s" does not exist.', $name)); 66 | } 67 | 68 | return $this->views[$name]; 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function remove($name) 75 | { 76 | if (!$this->has($name)) { 77 | throw new InvalidArgumentException(sprintf('The view "%s" does not exist.', $name)); 78 | } 79 | 80 | unset($this->views[$name]); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function all() 87 | { 88 | return $this->views; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function clear() 95 | { 96 | $this->views = array(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/ViewFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | /** 15 | * ViewFactoryInterface. 16 | * 17 | * @author Pablo Díez 18 | */ 19 | interface ViewFactoryInterface 20 | { 21 | /** 22 | * Sets a view. 23 | * 24 | * @param string $name The view name. 25 | * @param ViewInterface $view The view. 26 | */ 27 | public function set($name, ViewInterface $view); 28 | 29 | /** 30 | * Returns whether a view exists or not. 31 | * 32 | * @param string $name The name. 33 | * 34 | * @return boolean Whether a view exists or not. 35 | */ 36 | public function has($name); 37 | 38 | /** 39 | * Adds views. 40 | * 41 | * @param array $views An array of views. 42 | */ 43 | public function add(array $views); 44 | 45 | /** 46 | * Returns a view. 47 | * 48 | * @param string $name The name. 49 | * 50 | * @return ViewInterface The view. 51 | * 52 | * @throws \InvalidArgumentException If the view does not exist. 53 | */ 54 | public function get($name); 55 | 56 | /** 57 | * Returns all the views. 58 | * 59 | * @return array The views. 60 | */ 61 | public function all(); 62 | 63 | /** 64 | * Removes a view. 65 | * 66 | * @param string $name The name. 67 | * 68 | * @throws \InvalidArgumentException If the view does not exist. 69 | */ 70 | public function remove($name); 71 | 72 | /** 73 | * Clears the views. 74 | */ 75 | public function clear(); 76 | } 77 | -------------------------------------------------------------------------------- /src/Pagerfanta/View/ViewInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pagerfanta\View; 13 | 14 | use Pagerfanta\PagerfantaInterface; 15 | 16 | /** 17 | * ViewInterface. 18 | * 19 | * @author Pablo Díez 20 | */ 21 | interface ViewInterface 22 | { 23 | /** 24 | * Renders a pagerfanta. 25 | * 26 | * The route generator can be any callable to generate 27 | * the routes receiving the page number as first and 28 | * unique argument. 29 | * 30 | * @param PagerfantaInterface $pagerfanta A pagerfanta. 31 | * @param callable $routeGenerator A callable to generate the routes. 32 | * @param array $options An array of options (optional). 33 | */ 34 | public function render(PagerfantaInterface $pagerfanta, $routeGenerator, array $options = array()); 35 | 36 | /** 37 | * Returns the canonical name. 38 | * 39 | * @return string The canonical name. 40 | */ 41 | public function getName(); 42 | } 43 | --------------------------------------------------------------------------------