src/PaginationFactory.php
public function create(\Psr\Http\Message\ServerRequestInterface $request, string $sortParameter = self::SORT, array $defaultSort = []): Pagination
    {
        $offset = 0;
        $max = PHP_INT_MAX;
        
        $params = $request->getQueryParams();
        $range = $request->getHeaderLine(self::RANGE);
        
        if ($range !== null && preg_match(self::REGEX_RANGE, $range, $rm)) {
            // dojo.store.JsonRest style
            $offset = (int)$rm[1];
            $max = (int)$rm[2] - $offset + 1;
        } else {
            $max = $this->parse(self::$maxAlias, $params, PHP_INT_MAX);
            // Grails, ExtJS, dojox.data.QueryReadStore, all zero-based
            $offVal = $this->parse(self::$offsetAlias, $params, 0);
            if ($offVal > 0) {
                $offset = $offVal;
            } elseif (isset($params[self::START_INDEX])) {
            // OpenSearch style, 1-based
                $startIdx = isset($params[self::START_INDEX]) ? (int)$params[self::START_INDEX] : 0;
                if ($startIdx > 0) {
                    $offset = $startIdx - 1;
                }
            } elseif (isset($params[self::START_PAGE]) || isset($params[self::PAGE])) {
            // OpenSearch or Spring Data style, 1-based
                $startPage = $this->parse(self::$pageAlias, $params, 0);
                if ($startPage > 0) {
                    $offset = ($max * ($startPage - 1));
                }
            }
        }
        
        return new Pagination($max, $offset, $this->getOrder($request, $sortParameter, $defaultSort));
    }
public function create(\Psr\Http\Message\ServerRequestInterface $request, string $sortParameter = self::SORT, array $defaultSort = []): Pagination
    {
        $offset = 0;
        $max = PHP_INT_MAX;
        
        $params = $request->getQueryParams();
        $range = $request->getHeaderLine(self::RANGE);
        
        if ($range !== null && preg_match(self::REGEX_RANGE, $range, $rm)) {
            // dojo.store.JsonRest style
            $offset = (int)$rm[1];
            $max = (int)$rm[2] - $offset + 1;
        } else {
            $max = $this->parse(self::$maxAlias, $params, PHP_INT_MAX);
            // Grails, ExtJS, dojox.data.QueryReadStore, all zero-based
            $offVal = $this->parse(self::$offsetAlias, $params, 0);
            if ($offVal > 0) {
                $offset = $offVal;
            } elseif (isset($params[self::START_INDEX])) {
            // OpenSearch style, 1-based
                $startIdx = isset($params[self::START_INDEX]) ? (int)$params[self::START_INDEX] : 0;
                if ($startIdx > 0) {
                    $offset = $startIdx - 1;
                }
            } elseif (isset($params[self::START_PAGE]) || isset($params[self::PAGE])) {
            // OpenSearch or Spring Data style, 1-based
                $startPage = $this->parse(self::$pageAlias, $params, 0);
                if ($startPage > 0) {
                    $offset = ($max * ($startPage - 1));
                }
            }
        }
        
        return new Pagination($max, $offset, $this->getOrder($request, $sortParameter, $defaultSort));
    }
Checks the request query parameters and headers for pagination info.

This class supports a good size sampling of different ways to provide
pagination info.

#### Range
- `max` + `offset` (e.g. Grails): `&max=25&offset=0`
- `count` + `start` (e.g. `dojox.data.QueryReadStore`): `&count=25&start=0`
- `Range` header (e.g. `dojo.store.JsonRest`): `Range: items=0-24`
- `count` + `startIndex` (e.g. OpenSearch): `&count=25&startIndex=1`
- `count` + `startPage` (e.g. OpenSearch): `&count=25&startPage=1`
- `limit` + `page` (e.g. Spring Data REST): `&limit=25&page=1`
- `limit` + `start` (e.g. ExtJS): `&limit=25&start=0`

Dojo, Grails, and ExtJS are all zero-based. OpenSearch and Spring Data
are one-based.

#### Order
- OpenSearchServer: `&sort=foo&sort=-bar`
- OpenSearch extension: `&sort=foo:ascending&sort=bar:descending`
- Grails: `&sort=foo&order=asc`
- Spring Data REST: `&sort=foo,asc&sort=bar,desc`
- Dojo: `&sort(+foo,-bar)`
- Dojo w/field: `&[field]=+foo,-bar`
- ExtJS JSON: `&sort=[{"property":"foo","direction":"asc"},{"property":"bar","direction":"desc"}]`

Because of the fact that many order syntaxes use multiple query string
parameters with the same name, it is absolutely *vital* that you do not
use a `ServerRequestInterface` that has been constructed with the `$_GET`
superglobal.

The problem here is that PHP will overwrite entries in the `$_GET`
superglobal if they share the same name. With PHP, a request to
`file.php?foobar=foo&foobar=bar` will result in `$_GET` set to
`['foobar' => 'bar']`.

Other platforms, like the Java Servlet specification, allow list-like
access to these parameters. Make sure the object you pass for `$request`
has a `queryParams` property that has been created to account for
multiple query parameters with the same name. The `QueryParams` class
will produce an array that accounts for this case.

@param \Psr\Http\Message\ServerRequestInterface $request The server request. Please read important docs above.
@param string $sortParameter The name of the sort parameter
@param array<string,bool> $defaultSort The default sort if request lacks it
@return \Caridea\Http\Pagination The pagination details