CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.8 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.8
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • ConnectionManager
  • ConnectionRegistry
  • FactoryLocator
  • Paginator
  • QueryCacher
  • ResultSetDecorator
  • RulesChecker

Interfaces

  • ConnectionInterface
  • EntityInterface
  • FixtureInterface
  • InvalidPropertyInterface
  • PaginatorInterface
  • QueryInterface
  • RepositoryInterface
  • ResultSetInterface
  • SchemaInterface
  • TableSchemaInterface

Traits

  • EntityTrait
  • ModelAwareTrait
  • QueryTrait
  • RulesAwareTrait
  1: <?php
  2: /**
  3:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4:  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5:  *
  6:  * Licensed under The MIT License
  7:  * For full copyright and license information, please see the LICENSE.txt
  8:  * Redistributions of files must retain the above copyright notice.
  9:  *
 10:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 11:  * @link          http://cakephp.org CakePHP(tm) Project
 12:  * @since         3.5.0
 13:  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 14:  */
 15: namespace Cake\Datasource;
 16: 
 17: use Cake\Core\InstanceConfigTrait;
 18: use Cake\Datasource\Exception\PageOutOfBoundsException;
 19: 
 20: /**
 21:  * This class is used to handle automatic model data pagination.
 22:  */
 23: class Paginator implements PaginatorInterface
 24: {
 25:     use InstanceConfigTrait;
 26: 
 27:     /**
 28:      * Default pagination settings.
 29:      *
 30:      * When calling paginate() these settings will be merged with the configuration
 31:      * you provide.
 32:      *
 33:      * - `maxLimit` - The maximum limit users can choose to view. Defaults to 100
 34:      * - `limit` - The initial number of items per page. Defaults to 20.
 35:      * - `page` - The starting page, defaults to 1.
 36:      * - `whitelist` - A list of parameters users are allowed to set using request
 37:      *   parameters. Modifying this list will allow users to have more influence
 38:      *   over pagination, be careful with what you permit.
 39:      *
 40:      * @var array
 41:      */
 42:     protected $_defaultConfig = [
 43:         'page' => 1,
 44:         'limit' => 20,
 45:         'maxLimit' => 100,
 46:         'whitelist' => ['limit', 'sort', 'page', 'direction']
 47:     ];
 48: 
 49:     /**
 50:      * Paging params after pagination operation is done.
 51:      *
 52:      * @var array
 53:      */
 54:     protected $_pagingParams = [];
 55: 
 56:     /**
 57:      * Handles automatic pagination of model records.
 58:      *
 59:      * ### Configuring pagination
 60:      *
 61:      * When calling `paginate()` you can use the $settings parameter to pass in
 62:      * pagination settings. These settings are used to build the queries made
 63:      * and control other pagination settings.
 64:      *
 65:      * If your settings contain a key with the current table's alias. The data
 66:      * inside that key will be used. Otherwise the top level configuration will
 67:      * be used.
 68:      *
 69:      * ```
 70:      *  $settings = [
 71:      *    'limit' => 20,
 72:      *    'maxLimit' => 100
 73:      *  ];
 74:      *  $results = $paginator->paginate($table, $settings);
 75:      * ```
 76:      *
 77:      * The above settings will be used to paginate any repository. You can configure
 78:      * repository specific settings by keying the settings with the repository alias.
 79:      *
 80:      * ```
 81:      *  $settings = [
 82:      *    'Articles' => [
 83:      *      'limit' => 20,
 84:      *      'maxLimit' => 100
 85:      *    ],
 86:      *    'Comments' => [ ... ]
 87:      *  ];
 88:      *  $results = $paginator->paginate($table, $settings);
 89:      * ```
 90:      *
 91:      * This would allow you to have different pagination settings for
 92:      * `Articles` and `Comments` repositories.
 93:      *
 94:      * ### Controlling sort fields
 95:      *
 96:      * By default CakePHP will automatically allow sorting on any column on the
 97:      * repository object being paginated. Often times you will want to allow
 98:      * sorting on either associated columns or calculated fields. In these cases
 99:      * you will need to define a whitelist of all the columns you wish to allow
100:      * sorting on. You can define the whitelist in the `$settings` parameter:
101:      *
102:      * ```
103:      * $settings = [
104:      *   'Articles' => [
105:      *     'finder' => 'custom',
106:      *     'sortWhitelist' => ['title', 'author_id', 'comment_count'],
107:      *   ]
108:      * ];
109:      * ```
110:      *
111:      * Passing an empty array as whitelist disallows sorting altogether.
112:      *
113:      * ### Paginating with custom finders
114:      *
115:      * You can paginate with any find type defined on your table using the
116:      * `finder` option.
117:      *
118:      * ```
119:      *  $settings = [
120:      *    'Articles' => [
121:      *      'finder' => 'popular'
122:      *    ]
123:      *  ];
124:      *  $results = $paginator->paginate($table, $settings);
125:      * ```
126:      *
127:      * Would paginate using the `find('popular')` method.
128:      *
129:      * You can also pass an already created instance of a query to this method:
130:      *
131:      * ```
132:      * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
133:      *   return $q->where(['name' => 'CakePHP'])
134:      * });
135:      * $results = $paginator->paginate($query);
136:      * ```
137:      *
138:      * ### Scoping Request parameters
139:      *
140:      * By using request parameter scopes you can paginate multiple queries in
141:      * the same controller action:
142:      *
143:      * ```
144:      * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
145:      * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
146:      * ```
147:      *
148:      * Each of the above queries will use different query string parameter sets
149:      * for pagination data. An example URL paginating both results would be:
150:      *
151:      * ```
152:      * /dashboard?articles[page]=1&tags[page]=2
153:      * ```
154:      *
155:      * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate.
156:      * @param array $params Request params
157:      * @param array $settings The settings/configuration used for pagination.
158:      * @return \Cake\Datasource\ResultSetInterface Query results
159:      * @throws \Cake\Datasource\Exception\PageOutOfBoundsException
160:      */
161:     public function paginate($object, array $params = [], array $settings = [])
162:     {
163:         $query = null;
164:         if ($object instanceof QueryInterface) {
165:             $query = $object;
166:             $object = $query->getRepository();
167:         }
168: 
169:         $alias = $object->getAlias();
170:         $defaults = $this->getDefaults($alias, $settings);
171:         $options = $this->mergeOptions($params, $defaults);
172:         $options = $this->validateSort($object, $options);
173:         $options = $this->checkLimit($options);
174: 
175:         $options += ['page' => 1, 'scope' => null];
176:         $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page'];
177:         list($finder, $options) = $this->_extractFinder($options);
178: 
179:         if (empty($query)) {
180:             $query = $object->find($finder, $options);
181:         } else {
182:             $query->applyOptions($options);
183:         }
184: 
185:         $cleanQuery = clone $query;
186:         $results = $query->all();
187:         $numResults = count($results);
188:         $count = $cleanQuery->count();
189: 
190:         $page = $options['page'];
191:         $limit = $options['limit'];
192:         $pageCount = max((int)ceil($count / $limit), 1);
193:         $requestedPage = $page;
194:         $page = min($page, $pageCount);
195: 
196:         $order = (array)$options['order'];
197:         $sortDefault = $directionDefault = false;
198:         if (!empty($defaults['order']) && count($defaults['order']) === 1) {
199:             $sortDefault = key($defaults['order']);
200:             $directionDefault = current($defaults['order']);
201:         }
202: 
203:         $start = 0;
204:         if ($count >= 1) {
205:             $start = (($page - 1) * $limit) + 1;
206:         }
207:         $end = $start + $limit - 1;
208:         if ($count < $end) {
209:             $end = $count;
210:         }
211: 
212:         $paging = [
213:             'finder' => $finder,
214:             'page' => $page,
215:             'current' => $numResults,
216:             'count' => $count,
217:             'perPage' => $limit,
218:             'start' => $start,
219:             'end' => $end,
220:             'prevPage' => $page > 1,
221:             'nextPage' => $count > ($page * $limit),
222:             'pageCount' => $pageCount,
223:             'sort' => $options['sort'],
224:             'direction' => isset($options['sort']) ? current($order) : null,
225:             'limit' => $defaults['limit'] != $limit ? $limit : null,
226:             'sortDefault' => $sortDefault,
227:             'directionDefault' => $directionDefault,
228:             'scope' => $options['scope'],
229:             'completeSort' => $order,
230:         ];
231: 
232:         $this->_pagingParams = [$alias => $paging];
233: 
234:         if ($requestedPage > $page) {
235:             throw new PageOutOfBoundsException([
236:                 'requestedPage' => $requestedPage,
237:                 'pagingParams' => $this->_pagingParams
238:             ]);
239:         }
240: 
241:         return $results;
242:     }
243: 
244:     /**
245:      * Extracts the finder name and options out of the provided pagination options.
246:      *
247:      * @param array $options the pagination options.
248:      * @return array An array containing in the first position the finder name
249:      *   and in the second the options to be passed to it.
250:      */
251:     protected function _extractFinder($options)
252:     {
253:         $type = !empty($options['finder']) ? $options['finder'] : 'all';
254:         unset($options['finder'], $options['maxLimit']);
255: 
256:         if (is_array($type)) {
257:             $options = (array)current($type) + $options;
258:             $type = key($type);
259:         }
260: 
261:         return [$type, $options];
262:     }
263: 
264:     /**
265:      * Get paging params after pagination operation.
266:      *
267:      * @return array
268:      */
269:     public function getPagingParams()
270:     {
271:         return $this->_pagingParams;
272:     }
273: 
274:     /**
275:      * Merges the various options that Paginator uses.
276:      * Pulls settings together from the following places:
277:      *
278:      * - General pagination settings
279:      * - Model specific settings.
280:      * - Request parameters
281:      *
282:      * The result of this method is the aggregate of all the option sets
283:      * combined together. You can change config value `whitelist` to modify
284:      * which options/values can be set using request parameters.
285:      *
286:      * @param array $params Request params.
287:      * @param array $settings The settings to merge with the request data.
288:      * @return array Array of merged options.
289:      */
290:     public function mergeOptions($params, $settings)
291:     {
292:         if (!empty($settings['scope'])) {
293:             $scope = $settings['scope'];
294:             $params = !empty($params[$scope]) ? (array)$params[$scope] : [];
295:         }
296:         $params = array_intersect_key($params, array_flip($this->getConfig('whitelist')));
297: 
298:         return array_merge($settings, $params);
299:     }
300: 
301:     /**
302:      * Get the settings for a $model. If there are no settings for a specific
303:      * repository, the general settings will be used.
304:      *
305:      * @param string $alias Model name to get settings for.
306:      * @param array $settings The settings which is used for combining.
307:      * @return array An array of pagination settings for a model,
308:      *   or the general settings.
309:      */
310:     public function getDefaults($alias, $settings)
311:     {
312:         if (isset($settings[$alias])) {
313:             $settings = $settings[$alias];
314:         }
315: 
316:         $defaults = $this->getConfig();
317:         $maxLimit = isset($settings['maxLimit']) ? $settings['maxLimit'] : $defaults['maxLimit'];
318:         $limit = isset($settings['limit']) ? $settings['limit'] : $defaults['limit'];
319: 
320:         if ($limit > $maxLimit) {
321:             $limit = $maxLimit;
322:         }
323: 
324:         $settings['maxLimit'] = $maxLimit;
325:         $settings['limit'] = $limit;
326: 
327:         return $settings + $defaults;
328:     }
329: 
330:     /**
331:      * Validate that the desired sorting can be performed on the $object.
332:      *
333:      * Only fields or virtualFields can be sorted on. The direction param will
334:      * also be sanitized. Lastly sort + direction keys will be converted into
335:      * the model friendly order key.
336:      *
337:      * You can use the whitelist parameter to control which columns/fields are
338:      * available for sorting via URL parameters. This helps prevent users from ordering large
339:      * result sets on un-indexed values.
340:      *
341:      * If you need to sort on associated columns or synthetic properties you
342:      * will need to use a whitelist.
343:      *
344:      * Any columns listed in the sort whitelist will be implicitly trusted.
345:      * You can use this to sort on synthetic columns, or columns added in custom
346:      * find operations that may not exist in the schema.
347:      *
348:      * The default order options provided to paginate() will be merged with the user's
349:      * requested sorting field/direction.
350:      *
351:      * @param \Cake\Datasource\RepositoryInterface $object Repository object.
352:      * @param array $options The pagination options being used for this request.
353:      * @return array An array of options with sort + direction removed and
354:      *   replaced with order if possible.
355:      */
356:     public function validateSort(RepositoryInterface $object, array $options)
357:     {
358:         if (isset($options['sort'])) {
359:             $direction = null;
360:             if (isset($options['direction'])) {
361:                 $direction = strtolower($options['direction']);
362:             }
363:             if (!in_array($direction, ['asc', 'desc'])) {
364:                 $direction = 'asc';
365:             }
366: 
367:             $order = (isset($options['order']) && is_array($options['order'])) ? $options['order'] : [];
368:             if ($order && $options['sort'] && strpos($options['sort'], '.') === false) {
369:                 $order = $this->_removeAliases($order, $object->getAlias());
370:             }
371: 
372:             $options['order'] = [$options['sort'] => $direction] + $order;
373:         } else {
374:             $options['sort'] = null;
375:         }
376:         unset($options['direction']);
377: 
378:         if (empty($options['order'])) {
379:             $options['order'] = [];
380:         }
381:         if (!is_array($options['order'])) {
382:             return $options;
383:         }
384: 
385:         $inWhitelist = false;
386:         if (isset($options['sortWhitelist'])) {
387:             $field = key($options['order']);
388:             $inWhitelist = in_array($field, $options['sortWhitelist'], true);
389:             if (!$inWhitelist) {
390:                 $options['order'] = [];
391:                 $options['sort'] = null;
392: 
393:                 return $options;
394:             }
395:         }
396: 
397:         if ($options['sort'] === null
398:             && count($options['order']) === 1
399:             && !is_numeric(key($options['order']))
400:         ) {
401:             $options['sort'] = key($options['order']);
402:         }
403: 
404:         $options['order'] = $this->_prefix($object, $options['order'], $inWhitelist);
405: 
406:         return $options;
407:     }
408: 
409:     /**
410:      * Remove alias if needed.
411:      *
412:      * @param array $fields Current fields
413:      * @param string $model Current model alias
414:      * @return array $fields Unaliased fields where applicable
415:      */
416:     protected function _removeAliases($fields, $model)
417:     {
418:         $result = [];
419:         foreach ($fields as $field => $sort) {
420:             if (strpos($field, '.') === false) {
421:                 $result[$field] = $sort;
422:                 continue;
423:             }
424: 
425:             list ($alias, $currentField) = explode('.', $field);
426: 
427:             if ($alias === $model) {
428:                 $result[$currentField] = $sort;
429:                 continue;
430:             }
431: 
432:             $result[$field] = $sort;
433:         }
434: 
435:         return $result;
436:     }
437: 
438:     /**
439:      * Prefixes the field with the table alias if possible.
440:      *
441:      * @param \Cake\Datasource\RepositoryInterface $object Repository object.
442:      * @param array $order Order array.
443:      * @param bool $whitelisted Whether or not the field was whitelisted.
444:      * @return array Final order array.
445:      */
446:     protected function _prefix(RepositoryInterface $object, $order, $whitelisted = false)
447:     {
448:         $tableAlias = $object->getAlias();
449:         $tableOrder = [];
450:         foreach ($order as $key => $value) {
451:             if (is_numeric($key)) {
452:                 $tableOrder[] = $value;
453:                 continue;
454:             }
455:             $field = $key;
456:             $alias = $tableAlias;
457: 
458:             if (strpos($key, '.') !== false) {
459:                 list($alias, $field) = explode('.', $key);
460:             }
461:             $correctAlias = ($tableAlias === $alias);
462: 
463:             if ($correctAlias && $whitelisted) {
464:                 // Disambiguate fields in schema. As id is quite common.
465:                 if ($object->hasField($field)) {
466:                     $field = $alias . '.' . $field;
467:                 }
468:                 $tableOrder[$field] = $value;
469:             } elseif ($correctAlias && $object->hasField($field)) {
470:                 $tableOrder[$tableAlias . '.' . $field] = $value;
471:             } elseif (!$correctAlias && $whitelisted) {
472:                 $tableOrder[$alias . '.' . $field] = $value;
473:             }
474:         }
475: 
476:         return $tableOrder;
477:     }
478: 
479:     /**
480:      * Check the limit parameter and ensure it's within the maxLimit bounds.
481:      *
482:      * @param array $options An array of options with a limit key to be checked.
483:      * @return array An array of options for pagination.
484:      */
485:     public function checkLimit(array $options)
486:     {
487:         $options['limit'] = (int)$options['limit'];
488:         if (empty($options['limit']) || $options['limit'] < 1) {
489:             $options['limit'] = 1;
490:         }
491:         $options['limit'] = max(min($options['limit'], $options['maxLimit']), 1);
492: 
493:         return $options;
494:     }
495: }
496: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs