Commit 1b6480b6 authored by Bernhard Schussek's avatar Bernhard Schussek

Added basic pagination functionality

parent 2e069d4e
vendor
.php_cs.cache
<?php
/*
* This file is part of the CWD Data Doctrine ORM Bundle.
*
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$finder = PhpCsFixer\Finder::create()
->in([__DIR__])
;
return PhpCsFixer\Config::create()
->setUsingCache(true)
->setRiskyAllowed(true)
->setRules([
'@Symfony' => true,
'declare_strict_types' => true,
'header_comment' => [
'header' => <<<EOF
This file is part of the CWD Data Doctrine ORM Bundle
(c) cwd.at GmbH <office@cwd.at>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF
,
'location' => 'after_open',
],
])
->setFinder($finder)
;
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of neos-crm
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2017 Ludwig Ruderstaller <lr@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......@@ -13,11 +13,21 @@ declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\Repository;
use function array_slice;
use Cwd\DataBundle\Cursor\ArrayCursor;
use Cwd\DataBundle\Cursor\Cursor;
use Cwd\DataBundle\Cursor\EmptyCursor;
use Cwd\DataBundle\Id\Id;
use Cwd\DataBundle\Id\Identifiable;
use Cwd\DataBundle\Order\Order;
use Cwd\DataBundle\Pagination\Offset\Base64Offset;
use Cwd\DataBundle\Pagination\Offset\NumericOffset;
use Cwd\DataBundle\Pagination\Page\Item;
use Cwd\DataBundle\Pagination\Page\Page;
use Cwd\DataBundle\Pagination\PageRequest\AfterOffset;
use Cwd\DataBundle\Pagination\PageRequest\BeforeOffset;
use Cwd\DataBundle\Pagination\PageRequest\NumberedPage;
use Cwd\DataBundle\Pagination\PageRequest\PageRequest;
use Cwd\DataBundle\Repository\DuplicateValue;
use Cwd\DataBundle\Repository\EditableRepository;
use Cwd\DataBundle\Repository\NoResultFound;
......@@ -37,6 +47,7 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use LogicException;
/**
* Base class for repositories backed by Doctrine ORM.
......@@ -568,6 +579,202 @@ abstract class ORMRepository extends EntityRepository implements EditableReposit
{
}
protected function fetchPage(QueryBuilder $qb, string $alias, PageRequest $pageRequest = null, Order $order = null): Page
{
$countQb = clone $qb;
$totalCount = $countQb->select(sprintf('COUNT(%s)', $alias))
->getQuery()
->getSingleScalarResult();
if (0 === $totalCount) {
return Page::createEmpty();
}
if (null !== $pageRequest) {
$this->addPaginationCriteria($qb, $alias, $pageRequest, $order);
}
$results = $qb->getQuery()->execute();
if ($pageRequest instanceof BeforeOffset) {
// BeforeOffset is implemented as reverse AfterOffset search
$results = array_reverse($results);
}
switch (true) {
case $pageRequest instanceof BeforeOffset:
case $pageRequest instanceof AfterOffset:
$hasPreviousPage = false !== current($results);
$hasNextPage = count($results) > ($pageRequest->getNumberOfItems() + 1);
$items = array_map(
function ($result) {
return new Item($result[0], Base64Offset::fromDecodedString($result['offset']));
},
array_slice($results, 1, $pageRequest->getNumberOfItems())
);
break;
case $pageRequest instanceof NumberedPage:
$hasPreviousPage = $pageRequest->getPageNumber() > 0;
$hasNextPage = (($pageRequest->getPageNumber() + 1) * $pageRequest->getNumberOfItems()) < $totalCount;
$baseOffset = $pageRequest->getPageNumber() * $pageRequest->getNumberOfItems();
$items = array_map(
function ($result, int $index) use ($baseOffset) {
return new Item($result, new NumericOffset($index, $baseOffset + $index));
},
$results,
range(0, count($results) - 1)
);
break;
case null === $pageRequest:
$hasPreviousPage = false;
$hasNextPage = false;
$items = array_map(
function ($result, int $index) {
return new Item($result, new NumericOffset($index, $index));
},
$results,
range(0, count($results) - 1)
);
break;
default:
throw new LogicException(sprintf(
'Unsupported page request: %s',
get_class($pageRequest)
));
}
return new Page($items, $totalCount, $hasPreviousPage, $hasNextPage);
}
protected function addPaginationCriteria(QueryBuilder $qb, string $alias, PageRequest $pageRequest, Order $order = null)
{
switch (true) {
case $pageRequest instanceof AfterOffset:
$this->addSelectOffsetCriteria($qb, $alias, $order);
$this->addStartWithOffsetCriteria($qb, $alias, $pageRequest->getOffset(), $order);
$this->addOrderByCriteria($qb, $alias, $order);
// We need two more items to find out whether there is a
// previous/next page
$qb->setMaxResults($pageRequest->getNumberOfItems() + 2);
break;
case $pageRequest instanceof BeforeOffset:
// We do a reverse AfterOffset search
$order = $order->invert();
$this->addSelectOffsetCriteria($qb, $alias, $order);
$this->addStartWithOffsetCriteria($qb, $alias, $pageRequest->getOffset(), $order);
$this->addOrderByCriteria($qb, $alias, $order);
// We need two more items to find out whether there is a
// previous/next page
$qb->setMaxResults($pageRequest->getNumberOfItems() + 2);
break;
case $pageRequest instanceof NumberedPage:
if (null !== $order) {
$this->addOrderByCriteria($qb, $alias, $order);
}
$qb->setFirstResult($pageRequest->getPageNumber() * $pageRequest->getNumberOfItems());
$qb->setMaxResults($pageRequest->getNumberOfItems());
break;
default:
throw new LogicException(sprintf(
'Unsupported page request: %s',
get_class($pageRequest)
));
}
}
protected function addSelectOffsetCriteria(QueryBuilder $qb, string $alias, Order $order, string $delimiter = Base64Offset::DEFAULT_DELIMITER): void
{
// TODO add explicit casts
$fieldNames = $order->getFields();
$offsetField = count($fieldNames) > 1
? sprintf(
'CONCAT(%s)',
implode(
sprintf(', \'%s\', ', $delimiter),
array_map(
function ($fieldName) use ($alias) {
return sprintf('%s.%s', $alias, $fieldName);
},
$fieldNames
)
)
)
: sprintf('%s.%s', $alias, $fieldNames[0]);
$qb->addSelect(sprintf('%s as offset', $offsetField));
}
protected function addStartWithOffsetCriteria(QueryBuilder $qb, string $alias, Base64Offset $offset, Order $order): void
{
// TODO could we just compare the offset instead?
$fieldValues = $offset->getValues();
$orderDirections = array_values($order->getDirections());
$nextConditionPrefixes = [];
$i = 0;
foreach ($fieldValues as $index => $fieldValue) {
if (!isset($orderDirections[$index])) {
// More field values than sort fields
break;
}
$direction = $orderDirections[$index];
$conditionPrefix = count($nextConditionPrefixes) > 0
? '('.implode(' AND ', $nextConditionPrefixes).') OR '
: '';
$qb->andWhere($conditionPrefix.sprintf(
'%s.%s %s :fromOffset%s',
$alias,
$this->fieldNames[$i],
OrderDirection::ASCENDING === $direction ? '>=' : '<=',
$i
));
$qb->setParameter(
sprintf('fromOffset%s', $i),
$fieldValue
);
$nextConditionPrefixes[] = sprintf(
'%s.%s <> :fromOffset%s',
$alias,
$this->fieldNames[$i],
$i
);
++$i;
}
}
protected function addOrderByCriteria(QueryBuilder $qb, string $alias, Order $order): void
{
// TODO could we just order by the offset if there is one?
foreach ($order->getDirections() as $fieldName => $direction) {
$qb->addOrderBy(
sprintf('%s.%s', $alias, $fieldName),
$direction
);
}
}
protected function mapOrderBySpecification(OrderBy $orderBy): OrderBy
{
return $orderBy;
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
......
<?php
/*
* This file is part of the ÖWM API.
* This file is part of the CWD Data Doctrine ORM Bundle
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
* (c) cwd.at GmbH <office@cwd.at>