This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<?php
namespace Knp\Component\Pager\Event;
use Symfony\Component\EventDispatcher\Event;
use Knp\Component\Pager\Pagination\PaginationInterface;
/**
* Specific Event class for paginator
*/
class AfterEvent extends Event
{
private $pagination;
public function __construct(PaginationInterface $paginationView)
{
$this->pagination = $paginationView;
}
public function getPaginationView()
{
return $this->pagination;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Knp\Component\Pager\Event;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Specific Event class for paginator
*/
class BeforeEvent extends Event
{
private $eventDispatcher;
public function __construct($eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function getEventDispatcher()
{
return $this->eventDispatcher;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Knp\Component\Pager\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Specific Event class for paginator
*/
class ItemsEvent extends Event
{
/**
* A target being paginated
*
* @var mixed
*/
public $target;
/**
* List of options
*
* @var array
*/
public $options;
/**
* Items result
*
* @var mixed
*/
public $items;
/**
* Count result
*
* @var integer
*/
public $count;
private $offset;
private $limit;
private $customPaginationParams = array();
public function __construct($offset, $limit)
{
$this->offset = $offset;
$this->limit = $limit;
}
public function setCustomPaginationParameter($name, $value)
{
$this->customPaginationParams[$name] = $value;
}
public function getCustomPaginationParameters()
{
return $this->customPaginationParams;
}
public function unsetCustomPaginationParameter($name)
{
if (isset($this->customPaginationParams[$name])) {
unset($this->customPaginationParams[$name]);
}
}
public function getLimit()
{
return $this->limit;
}
public function getOffset()
{
return $this->offset;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Knp\Component\Pager\Event;
use Symfony\Component\EventDispatcher\Event;
use Knp\Component\Pager\Pagination\PaginationInterface;
/**
* Specific Event class for paginator
*/
class PaginationEvent extends Event
{
/**
* A target being paginated
*
* @var mixed
*/
public $target;
/**
* List of options
*
* @var array
*/
public $options;
private $pagination;
public function setPagination(PaginationInterface $pagination)
{
$this->pagination = $pagination;
}
public function getPagination()
{
return $this->pagination;
}
}

View File

@@ -0,0 +1,229 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\AST\WhereClause;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\AST\LikeExpression;
use Doctrine\ORM\Query\AST\ComparisonExpression;
use Doctrine\ORM\Query\AST\Literal;
use Doctrine\ORM\Query\AST\ConditionalExpression;
use Doctrine\ORM\Query\AST\ConditionalFactor;
use Doctrine\ORM\Query\AST\ConditionalPrimary;
use Doctrine\ORM\Query\AST\ConditionalTerm;
/**
* Where Query TreeWalker for Filtration functionality
* in doctrine paginator
*/
class WhereWalker extends TreeWalkerAdapter
{
/**
* Filter key columns hint name
*/
const HINT_PAGINATOR_FILTER_COLUMNS = 'knp_paginator.filter.columns';
/**
* Filter value hint name
*/
const HINT_PAGINATOR_FILTER_VALUE = 'knp_paginator.filter.value';
/**
* Walks down a SelectStatement AST node, modifying it to
* filter the query like requested by url
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$query = $this->_getQuery();
$queriedValue = $query->getHint(self::HINT_PAGINATOR_FILTER_VALUE);
$columns = $query->getHint(self::HINT_PAGINATOR_FILTER_COLUMNS);
$components = $this->_getQueryComponents();
$filterExpressions = array();
$expressions = array();
foreach ($columns as $column) {
$alias = false;
$parts = explode('.', $column);
$field = end($parts);
if (2 <= count($parts)) {
$alias = trim(reset($parts));
if (!array_key_exists($alias, $components)) {
throw new \UnexpectedValueException("There is no component aliased by [{$alias}] in the given Query");
}
$meta = $components[$alias];
if (!$meta['metadata']->hasField($field)) {
throw new \UnexpectedValueException("There is no such field [{$field}] in the given Query component, aliased by [$alias]");
}
$pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD, $alias, $field);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
} else {
if (!array_key_exists($field, $components)) {
throw new \UnexpectedValueException("There is no component field [{$field}] in the given Query");
}
$pathExpression = $components[$field]['resultVariable'];
}
$expression = new ConditionalPrimary();
if (isset($meta) && $meta['metadata']->getTypeOfField($field) === 'boolean') {
if (in_array(strtolower($queriedValue), array('true', 'false'))) {
$expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::BOOLEAN, $queriedValue));
} elseif (is_numeric($queriedValue)) {
$expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::BOOLEAN, $queriedValue == '1' ? 'true' : 'false'));
} else {
continue;
}
unset($meta);
} elseif (is_numeric($queriedValue)) {
$expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::NUMERIC, $queriedValue));
} else {
$expression->simpleConditionalExpression = new LikeExpression($pathExpression, new Literal(Literal::STRING, $queriedValue));
}
$filterExpressions[] = $expression->simpleConditionalExpression;
$expressions[] = $expression;
}
if (count($expressions) > 1) {
$conditionalPrimary = new ConditionalExpression($expressions);
} elseif (count($expressions) > 0) {
$conditionalPrimary = reset($expressions);
} else {
return;
}
if ($AST->whereClause) {
if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) {
if (!$this->termContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) {
array_unshift(
$AST->whereClause->conditionalExpression->conditionalFactors,
$this->createPrimaryFromNode($conditionalPrimary)
);
}
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) {
if (!$this->primaryContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) {
$AST->whereClause->conditionalExpression = new ConditionalTerm(array(
$this->createPrimaryFromNode($conditionalPrimary),
$AST->whereClause->conditionalExpression,
));
}
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) {
if (!$this->expressionContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) {
$previousPrimary = new ConditionalPrimary();
$previousPrimary->conditionalExpression = $AST->whereClause->conditionalExpression;
$AST->whereClause->conditionalExpression = new ConditionalTerm(array(
$this->createPrimaryFromNode($conditionalPrimary),
$previousPrimary,
));
}
}
} else {
$AST->whereClause = new WhereClause(
$conditionalPrimary
);
}
}
/**
* @param ConditionalExpression $node
* @param Node[] $filterExpressions
* @return bool
*/
private function expressionContainsFilter(ConditionalExpression $node, $filterExpressions)
{
foreach ($node->conditionalTerms as $conditionalTerm) {
if ($conditionalTerm instanceof ConditionalTerm && $this->termContainsFilter($conditionalTerm, $filterExpressions)) {
return true;
} elseif ($conditionalTerm instanceof ConditionalPrimary && $this->primaryContainsFilter($conditionalTerm, $filterExpressions)) {
return true;
}
}
return false;
}
/**
* @param ConditionalTerm $node
* @param Node[] $filterExpressions
* @return bool|void
*/
private function termContainsFilter(ConditionalTerm $node, $filterExpressions)
{
foreach ($node->conditionalFactors as $conditionalFactor) {
if ($conditionalFactor instanceof ConditionalFactor) {
if ($this->factorContainsFilter($conditionalFactor, $filterExpressions)) {
return true;
}
} elseif ($conditionalFactor instanceof ConditionalPrimary) {
if ($this->primaryContainsFilter($conditionalFactor, $filterExpressions)) {
return true;
}
}
}
return false;
}
/**
* @param ConditionalFactor $node
* @param Node[] $filterExpressions
* @return bool
*/
private function factorContainsFilter(ConditionalFactor $node, $filterExpressions)
{
if ($node->conditionalPrimary instanceof ConditionalPrimary && $node->not === false) {
return $this->primaryContainsFilter($node->conditionalPrimary, $filterExpressions);
}
return false;
}
/**
* @param ConditionalPrimary $node
* @param Node[] $filterExpressions
* @return bool
*/
private function primaryContainsFilter(ConditionalPrimary $node, $filterExpressions)
{
if ($node->isSimpleConditionalExpression() && ($node->simpleConditionalExpression instanceof LikeExpression || $node->simpleConditionalExpression instanceof ComparisonExpression)) {
return $this->isExpressionInFilterExpressions($node->simpleConditionalExpression, $filterExpressions);
}
if ($node->isConditionalExpression()) {
return $this->expressionContainsFilter($node->conditionalExpression, $filterExpressions);
}
return false;
}
/**
* @param Node $node
* @param Node[] $filterExpressions
* @return bool
*/
private function isExpressionInFilterExpressions(Node $node, $filterExpressions)
{
foreach ($filterExpressions as $filterExpression) {
if ((string) $filterExpression === (string) $node) {
return true;
}
}
return false;
}
/**
* @param Node $node
* @return ConditionalPrimary
*/
private function createPrimaryFromNode($node)
{
if ($node instanceof ConditionalPrimary) {
$conditionalPrimary = $node;
} else {
$conditionalPrimary = new ConditionalPrimary();
$conditionalPrimary->conditionalExpression = $node;
}
return $conditionalPrimary;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM;
use Doctrine\ORM\Query;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query\WhereWalker;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\Helper as QueryHelper;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class QuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof Query) {
if (!isset($_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]]) || (empty($_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]]) && $_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]] !== "0")) {
return;
}
if (!empty($_GET[$event->options[PaginatorInterface::FILTER_FIELD_PARAMETER_NAME]])) {
$columns = $_GET[$event->options[PaginatorInterface::FILTER_FIELD_PARAMETER_NAME]];
} elseif (!empty($event->options[PaginatorInterface::DEFAULT_FILTER_FIELDS])) {
$columns = $event->options[PaginatorInterface::DEFAULT_FILTER_FIELDS];
} else {
return;
}
$value = $_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]];
if (false !== strpos($value, '*')) {
$value = str_replace('*', '%', $value);
}
if (is_string($columns) && false !== strpos($columns, ',')) {
$columns = explode(',', $columns);
}
$columns = (array) $columns;
if (isset($event->options[PaginatorInterface::FILTER_FIELD_WHITELIST])) {
foreach ($columns as $column) {
if (!in_array($column, $event->options[PaginatorInterface::FILTER_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot filter by: [{$column}] this field is not in whitelist");
}
}
}
$event->target
->setHint(WhereWalker::HINT_PAGINATOR_FILTER_VALUE, $value)
->setHint(WhereWalker::HINT_PAGINATOR_FILTER_COLUMNS, $columns);
QueryHelper::addCustomTreeWalker($event->target, 'Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query\WhereWalker');
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0),
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Filtration;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\BeforeEvent;
class FiltrationSubscriber implements EventSubscriberInterface
{
/**
* Lazy-load state tracker
* @var bool
*/
private $isLoaded = false;
public function before(BeforeEvent $event)
{
// Do not lazy-load more than once
if ($this->isLoaded) {
return;
}
$disp = $event->getEventDispatcher();
// hook all standard filtration subscribers
$disp->addSubscriber(new Doctrine\ORM\QuerySubscriber());
$disp->addSubscriber(new PropelQuerySubscriber());
$this->isLoaded = true;
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.before' => array('before', 1),
);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Filtration;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
class PropelQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
$query = $event->target;
if ($query instanceof \ModelCriteria) {
if (empty($_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]])) {
return;
}
if (!empty($_GET[$event->options[PaginatorInterface::FILTER_FIELD_PARAMETER_NAME]])) {
$columns = $_GET[$event->options[PaginatorInterface::FILTER_FIELD_PARAMETER_NAME]];
} elseif (!empty($event->options[PaginatorInterface::DEFAULT_FILTER_FIELDS])) {
$columns = $event->options[PaginatorInterface::DEFAULT_FILTER_FIELDS];
} else {
return;
}
if (is_string($columns) && false !== strpos($columns, ',')) {
$columns = explode(',', $columns);
}
$columns = (array) $columns;
if (isset($event->options[PaginatorInterface::FILTER_FIELD_WHITELIST])) {
foreach ($columns as $column) {
if (!in_array($column, $event->options[PaginatorInterface::FILTER_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot filter by: [{$column}] this field is not in whitelist");
}
}
}
$value = $_GET[$event->options[PaginatorInterface::FILTER_VALUE_PARAMETER_NAME]];
$criteria = \Criteria::EQUAL;
if (false !== strpos($value, '*')) {
$value = str_replace('*', '%', $value);
$criteria = \Criteria::LIKE;
}
foreach ($columns as $column) {
if (false !== strpos($column, '.')) {
$query->where($column.$criteria.'?', $value);
} else {
$query->{'filterBy'.$column}($value, $criteria);
}
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0),
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use ArrayObject;
class ArraySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if (is_array($event->target)) {
$event->count = count($event->target);
$event->items = array_slice(
$event->target,
$event->getOffset(),
$event->getLimit()
);
$event->stopPropagation();
} elseif ($event->target instanceof ArrayObject) {
$event->count = $event->target->count();
$event->items = new ArrayObject(array_slice(
$event->target->getArrayCopy(),
$event->getOffset(),
$event->getLimit()
));
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', -1/* other data arrays should be analized first*/)
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use ArrayObject;
use Doctrine\Common\Collections\Collection;
class CollectionSubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof Collection) {
$event->count = $event->target->count();
$event->items = new ArrayObject($event->target->slice(
$event->getOffset(),
$event->getLimit()
));
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Doctrine\DBAL\Query\QueryBuilder;
/**
* DBALQueryBuilderSubscriber.php
*
* @author Vladimir Chub <v@chub.com.ua>
*/
class DBALQueryBuilderSubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof QueryBuilder) {
/** @var $target QueryBuilder */
$target = $event->target;
// count results
$qb = clone $target;
//reset count orderBy since it can break query and slow it down
$qb
->resetQueryPart('orderBy')
;
// get the query
$sql = $qb->getSQL();
$qb
->resetQueryParts()
->select('count(*) as cnt')
->from('(' . $sql . ')', 'dbal_count_tbl')
;
$event->count = $qb
->execute()
->fetchColumn(0)
;
// if there is results
$event->items = array();
if ($event->count) {
$qb = clone $target;
$qb
->setFirstResult($event->getOffset())
->setMaxResults($event->getLimit())
;
$event->items = $qb
->execute()
->fetchAll()
;
}
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 10 /*make sure to transform before any further modifications*/)
);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ODM\MongoDB;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Doctrine\ODM\MongoDB\Query\Builder;
class QueryBuilderSubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof Builder) {
// change target into query
$event->target = $event->target->getQuery();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 10/*make sure to transform before any further modifications*/)
);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ODM\MongoDB;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Doctrine\ODM\MongoDB\Query\Query;
class QuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof Query) {
// items
$type = $event->target->getType();
if ($type !== Query::TYPE_FIND) {
throw new \UnexpectedValueException('ODM query must be a FIND type query');
}
static $reflectionProperty;
if (is_null($reflectionProperty)) {
$reflectionClass = new \ReflectionClass('Doctrine\MongoDB\Query\Query');
$reflectionProperty = $reflectionClass->getProperty('query');
$reflectionProperty->setAccessible(true);
}
$queryOptions = $reflectionProperty->getValue($event->target);
$queryOptions['limit'] = $event->getLimit();
$queryOptions['skip'] = $event->getOffset();
$resultQuery = clone $event->target;
$reflectionProperty->setValue($resultQuery, $queryOptions);
$cursor = $resultQuery->execute();
// set the count from the cursor
$event->count = $cursor->count();
$event->items = array();
// iterator_to_array for GridFS results in 1 item
foreach ($cursor as $item) {
$event->items[] = $item;
}
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ODM\PHPCR;
use Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
/**
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class QueryBuilderSubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if (!$event->target instanceof QueryBuilder) {
return;
}
$event->target = $event->target->getQuery();
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 10/*make sure to transform before any further modifications*/)
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ODM\PHPCR;
use Doctrine\ODM\PHPCR\Query\Query;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
/**
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class QuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if (!$event->target instanceof Query) {
return;
}
$queryCount = clone $event->target;
$event->count = $queryCount->execute(null, Query::HYDRATE_PHPCR)->getRows()->count();
$query = $event->target;
$query->setMaxResults($event->getLimit());
$query->setFirstResult($event->getOffset());
$event->items = $query->execute();
$event->stopPropagation();
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
/**
* As is hydrator to fetch count query result without resultSetMappings etc.
*
* @author Vladimir Chub <v@chub.com.ua>
*/
class AsIsHydrator extends AbstractHydrator
{
/**
* Hydrates all rows from the current statement instance at once.
*/
protected function hydrateAllData()
{
return $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* DoctrineExtensions Paginate
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to kontakt@beberlei.de so I can send you a copy immediately.
*/
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query;
use Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\SelectExpression,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\AggregateExpression;
/**
* Replaces the selectClause of the AST with a COUNT statement
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
* @deprecated
*/
class CountWalker extends TreeWalkerAdapter
{
/**
* Distinct mode hint name
*/
const HINT_DISTINCT = 'knp_paginator.distinct';
/**
* Walks down a SelectStatement AST node, modifying it to retrieve a COUNT
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$rootComponents = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
$isParent = array_key_exists('parent', $qComp)
&& $qComp['parent'] === null
&& $qComp['nestingLevel'] == 0
;
if ($isParent) {
$rootComponents[] = array($dqlAlias => $qComp);
}
}
if (count($rootComponents) > 1) {
throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
}
$root = reset($rootComponents);
$parentName = key($root);
$parent = current($root);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT);
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, $distinct), null
)
);
// ORDER BY is not needed, only increases query execution through unnecessary sorting.
$AST->orderByClause = null;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query;
use Doctrine\ORM\Query;
/**
* ORM Query helper for cloning
* and hint processing
*/
class Helper
{
/**
* Clones the given $query and copies all used
* parameters and hints
*
* @param Query $query
* @return Query
*/
public static function cloneQuery(Query $query)
{
$clonedQuery = clone $query;
$clonedQuery->setParameters(clone $query->getParameters());
// attach hints
foreach ($query->getHints() as $name => $hint) {
$clonedQuery->setHint($name, $hint);
}
return $clonedQuery;
}
/**
* Add a custom TreeWalker $walker class name to
* be included in the CustomTreeWalker hint list
* of the given $query
*
* @param Query $query
* @param string $walker
* @return void
*/
public static function addCustomTreeWalker(Query $query, $walker)
{
$customTreeWalkers = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
if ($customTreeWalkers !== false && is_array($customTreeWalkers)) {
$customTreeWalkers = array_merge($customTreeWalkers, array($walker));
} else {
$customTreeWalkers = array($walker);
}
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $customTreeWalkers);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* DoctrineExtensions Paginate
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE. This license can also be viewed
* at http://hobodave.com/license.txt
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
* @deprecated
*/
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\SelectExpression,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\AggregateExpression;
/**
* Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
class LimitSubqueryWalker extends TreeWalkerAdapter
{
/**
* ID type hint
*/
const IDENTIFIER_TYPE = 'knp_paginator.id.type';
/**
* @var int Counter for generating unique order column aliases
*/
private $_aliasCounter = 0;
/**
* Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids
* of the root Entity
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$parent = null;
$parentName = null;
$selectExpressions = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
// preserve mixed data in query for ordering
if (isset($qComp['resultVariable'])) {
$selectExpressions[] = new SelectExpression($qComp['resultVariable'], $dqlAlias);
continue;
}
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
continue;
}
}
$identifier = $parent['metadata']->getSingleIdentifierFieldName();
$this->_getQuery()->setHint(
self::IDENTIFIER_TYPE,
Type::getType($parent['metadata']->getTypeOfField($identifier))
);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$parentName,
$identifier
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
array_unshift($selectExpressions, new SelectExpression($pathExpression, '_dctrn_id'));
$AST->selectClause->selectExpressions = $selectExpressions;
if (isset($AST->orderByClause)) {
foreach ($AST->orderByClause->orderByItems as $item) {
if ($item->expression instanceof PathExpression) {
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$item->expression->identificationVariable,
$item->expression->field
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions[] = new SelectExpression(
$pathExpression,
'_dctrn_ord' . $this->_aliasCounter++
);
}
}
}
$AST->selectClause->isDistinct = true;
}
}

View File

@@ -0,0 +1,140 @@
<?php
/**
* DoctrineExtensions Paginate
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE. This license can also be viewed
* at http://hobodave.com/license.txt
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\ArithmeticExpression;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\InExpression,
Doctrine\ORM\Query\AST\NullComparisonExpression,
Doctrine\ORM\Query\AST\InputParameter,
Doctrine\ORM\Query\AST\ConditionalPrimary,
Doctrine\ORM\Query\AST\ConditionalTerm,
Doctrine\ORM\Query\AST\ConditionalExpression,
Doctrine\ORM\Query\AST\ConditionalFactor,
Doctrine\ORM\Query\AST\WhereClause;
/**
* Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
* @deprecated
*/
class WhereInWalker extends TreeWalkerAdapter
{
/**
* ID Count hint name
*/
const HINT_PAGINATOR_ID_COUNT = 'knp_paginator.id.count';
/**
* Primary key alias for query
*/
const PAGINATOR_ID_ALIAS = 'dpid';
/**
* Replaces the whereClause in the AST
*
* Generates a clause equivalent to WHERE IN (:dpid_1, :dpid_2, ...)
*
* The parameter namespace (dpid) is defined by
* the PAGINATOR_ID_ALIAS
*
* The total number of parameters is retrieved from
* the HINT_PAGINATOR_ID_COUNT query hint
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$rootComponents = array();
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
$isParent = array_key_exists('parent', $qComp)
&& $qComp['parent'] === null
&& $qComp['nestingLevel'] == 0
;
if ($isParent) {
$rootComponents[] = array($dqlAlias => $qComp);
}
}
if (count($rootComponents) > 1) {
throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
}
$root = reset($rootComponents);
$parentName = key($root);
$parent = current($root);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD, $parentName, $parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT);
if ($count > 0) {
$arithmeticExpression = new ArithmeticExpression();
$arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression(array($pathExpression));
$expression = new InExpression($arithmeticExpression);
$ns = self::PAGINATOR_ID_ALIAS;
for ($i = 1; $i <= $count; $i++) {
$expression->literals[] = new InputParameter(":{$ns}_$i");
}
} else {
$expression = new NullComparisonExpression($pathExpression);
$expression->not = false;
}
$conditionalPrimary = new ConditionalPrimary;
$conditionalPrimary->simpleConditionalExpression = $expression;
if ($AST->whereClause) {
if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) {
$AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary;
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) {
$AST->whereClause->conditionalExpression = new ConditionalExpression(array(
new ConditionalTerm(array(
$AST->whereClause->conditionalExpression,
$conditionalPrimary
))
));
} elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) {
$tmpPrimary = new ConditionalPrimary;
$tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression;
$AST->whereClause->conditionalExpression = new ConditionalTerm(array(
$tmpPrimary,
$conditionalPrimary
));
}
} else {
$AST->whereClause = new WhereClause(
new ConditionalExpression(array(
new ConditionalTerm(array(
$conditionalPrimary
))
))
);
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Doctrine\ORM\QueryBuilder;
class QueryBuilderSubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof QueryBuilder) {
// change target into query
$event->target = $event->target->getQuery();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 10/*make sure to transform before any further modifications*/)
);
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\Query\Parameter;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\Helper as QueryHelper;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\CountWalker;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\WhereInWalker;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\LimitSubqueryWalker;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\CountWalker as DoctrineCountWalker;
use Doctrine\ORM\Tools\Pagination\WhereInWalker as DoctrineWhereInWalker;
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker as DoctrineLimitSubqueryWalker;
/**
* @deprecated see UsesPaginator
**/
class QuerySubscriber implements EventSubscriberInterface
{
/**
* Used if user set the count manually
*/
const HINT_COUNT = 'knp_paginator.count';
public function items(ItemsEvent $event)
{
if ($event->target instanceof Query) {
// process count
$useDoctrineWalkers = false;
$useDoctrineOutputWalker = false;
if (version_compare(\Doctrine\ORM\Version::VERSION, '2.3.0', '>=')) {
$useDoctrineWalkers = true;
$useDoctrineOutputWalker = true;
} else if (version_compare(\Doctrine\ORM\Version::VERSION, '2.2.0', '>=')) {
$useDoctrineWalkers = true;
}
if (($count = $event->target->getHint(self::HINT_COUNT)) !== false) {
$event->count = intval($count);
} else {
$countQuery = QueryHelper::cloneQuery($event->target);
if ($useDoctrineOutputWalker) {
$treeWalker = 'Doctrine\ORM\Tools\Pagination\CountOutputWalker';
$countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, $treeWalker);
} else if ($useDoctrineWalkers) {
QueryHelper::addCustomTreeWalker($countQuery, 'Doctrine\ORM\Tools\Pagination\CountWalker');
} else {
$treeWalker = 'Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\CountWalker';
QueryHelper::addCustomTreeWalker($countQuery, $treeWalker);
}
if ($useDoctrineWalkers) {
$countQuery->setHint(
DoctrineCountWalker::HINT_DISTINCT,
$event->options[PaginatorInterface::DISTINCT]
);
} else {
$countQuery->setHint(
CountWalker::HINT_DISTINCT,
$event->options[PaginatorInterface::DISTINCT]
);
}
$countQuery
->setFirstResult(null)
->setMaxResults(null)
;
$countQuery->getEntityManager()->getConfiguration()->addCustomHydrationMode('asIs',
'Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\AsIsHydrator');
$countResult = $countQuery->getResult('asIs');
$event->count = intval(current(current($countResult)));
}
// process items
$result = null;
if ($event->count) {
if ($event->options[PaginatorInterface::DISTINCT]) {
$limitSubQuery = QueryHelper::cloneQuery($event->target);
$limitSubQuery
->setFirstResult($event->getOffset())
->setMaxResults($event->getLimit())
->useQueryCache(false)
;
QueryHelper::addCustomTreeWalker($limitSubQuery, $useDoctrineWalkers ?
'Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker' :
'Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\LimitSubqueryWalker'
);
$ids = array_map('current', $limitSubQuery->getScalarResult());
// create where-in query
$whereInQuery = QueryHelper::cloneQuery($event->target);
QueryHelper::addCustomTreeWalker($whereInQuery, $useDoctrineWalkers ?
'Doctrine\ORM\Tools\Pagination\WhereInWalker' :
'Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\WhereInWalker'
);
$whereInQuery
->setHint($useDoctrineWalkers ?
DoctrineWhereInWalker::HINT_PAGINATOR_ID_COUNT :
WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)
)
->setFirstResult(null)
->setMaxResults(null)
;
if (version_compare(\Doctrine\ORM\Version::VERSION, '2.3.0', '>=') && count($ids) > 0) {
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
} else {
$type = $limitSubQuery->getHint($useDoctrineWalkers ?
DoctrineLimitSubqueryWalker::IDENTIFIER_TYPE :
LimitSubqueryWalker::IDENTIFIER_TYPE
);
$idAlias = $useDoctrineWalkers ?
DoctrineWhereInWalker::PAGINATOR_ID_ALIAS :
WhereInWalker::PAGINATOR_ID_ALIAS
;
foreach ($ids as $i => $id) {
$whereInQuery->setParameter(
$idAlias . '_' . ++$i,
$id,
$type->getName()
);
}
}
$result = $whereInQuery->execute();
} else {
$event->target
->setFirstResult($event->getOffset())
->setMaxResults($event->getLimit())
;
$result = $event->target->execute();
}
} else {
$result = array(); // count is 0
}
$event->items = $result;
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\QuerySubscriber;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\QuerySubscriber;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\ORM\Tools\Pagination\CountWalker;
class UsesPaginator implements EventSubscriberInterface
{
const HINT_FETCH_JOIN_COLLECTION = 'knp_paginator.fetch_join_collection';
public function items(ItemsEvent $event)
{
if (!class_exists('Doctrine\ORM\Tools\Pagination\Paginator')) {
return;
}
if (!$event->target instanceof Query) {
return;
}
$event->stopPropagation();
$useOutputWalkers = false;
if (isset($event->options['wrap-queries'])) {
$useOutputWalkers = $event->options['wrap-queries'];
}
$event->target
->setFirstResult($event->getOffset())
->setMaxResults($event->getLimit())
->setHint(CountWalker::HINT_DISTINCT, $event->options[PaginatorInterface::DISTINCT])
;
$fetchJoinCollection = true;
if ($event->target->hasHint(self::HINT_FETCH_JOIN_COLLECTION)) {
$fetchJoinCollection = $event->target->getHint(self::HINT_FETCH_JOIN_COLLECTION);
} else if (isset($event->options[PaginatorInterface::DISTINCT])) {
$fetchJoinCollection = $event->options[PaginatorInterface::DISTINCT];
}
$paginator = new Paginator($event->target, $fetchJoinCollection);
$paginator->setUseOutputWalkers($useOutputWalkers);
if (($count = $event->target->getHint(QuerySubscriber::HINT_COUNT)) !== false) {
$event->count = intval($count);
} else {
$event->count = count($paginator);
}
$event->items = iterator_to_array($paginator);
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate;
use Elastica\Query;
use Elastica\SearchableInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Elastica query pagination.
*
*/
class ElasticaQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if (is_array($event->target) && 2 === count($event->target) && reset($event->target) instanceof SearchableInterface && end($event->target) instanceof Query) {
list($searchable, $query) = $event->target;
$query->setFrom($event->getOffset());
$query->setSize($event->getLimit());
$results = $searchable->search($query);
$event->count = $results->getTotalHits();
if ($results->hasAggregations()) {
$event->setCustomPaginationParameter('aggregations', $results->getAggregations());
}
$event->setCustomPaginationParameter('resultSet', $results);
$event->items = $results->getResults();
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0) /* triggers before a standard array subscriber*/
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\PaginationEvent;
use Knp\Component\Pager\Event\BeforeEvent;
use Knp\Component\Pager\Pagination\SlidingPagination;
class PaginationSubscriber implements EventSubscriberInterface
{
/**
* Lazy-load state tracker
* @var bool
*/
private $isLoaded = false;
public function pagination(PaginationEvent $event)
{
$event->setPagination(new SlidingPagination);
$event->stopPropagation();
}
public function before(BeforeEvent $event)
{
// Do not lazy-load more than once
if ($this->isLoaded) {
return;
}
$disp = $event->getEventDispatcher();
// hook all standard subscribers
$disp->addSubscriber(new ArraySubscriber);
$disp->addSubscriber(new Doctrine\ORM\QueryBuilderSubscriber);
$disp->addSubscriber(new Doctrine\ORM\QuerySubscriber\UsesPaginator);
$disp->addSubscriber(new Doctrine\ORM\QuerySubscriber);
$disp->addSubscriber(new Doctrine\ODM\MongoDB\QueryBuilderSubscriber);
$disp->addSubscriber(new Doctrine\ODM\MongoDB\QuerySubscriber);
$disp->addSubscriber(new Doctrine\ODM\PHPCR\QueryBuilderSubscriber);
$disp->addSubscriber(new Doctrine\ODM\PHPCR\QuerySubscriber);
$disp->addSubscriber(new Doctrine\CollectionSubscriber);
$disp->addSubscriber(new Doctrine\DBALQueryBuilderSubscriber);
$disp->addSubscriber(new PropelQuerySubscriber);
$disp->addSubscriber(new SolariumQuerySubscriber());
$disp->addSubscriber(new ElasticaQuerySubscriber());
$this->isLoaded = true;
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.before' => array('before', 0),
'knp_pager.pagination' => array('pagination', 0)
);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate;
use ModelCriteria;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\PaginatorInterface;
class PropelQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if ($event->target instanceof ModelCriteria) {
// process count
$countQuery = clone $event->target;
$countQuery
->limit(0)
->offset(0)
;
if ($event->options[PaginatorInterface::DISTINCT]) {
$countQuery->distinct();
}
$event->count = intval($countQuery->count());
// process items
$result = null;
if ($event->count) {
$resultQuery = clone $event->target;
if ($event->options[PaginatorInterface::DISTINCT]) {
$resultQuery->distinct();
}
$resultQuery
->offset($event->getOffset())
->limit($event->getLimit())
;
$result = $resultQuery->find();
} else {
$result = array(); // count is 0
}
$event->items = $result;
$event->stopPropagation();
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0)
);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Paginate;
use Knp\Component\Pager\Event\ItemsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Solarium query pagination.
*
* @author Paweł Jędrzejewski <pjedrzejewski@diweb.pl>
*/
class SolariumQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
if (is_array($event->target) && 2 == count($event->target)) {
$values = array_values($event->target);
list($client, $query) = $values;
if ($client instanceof \Solarium\Client && $query instanceof \Solarium\QueryType\Select\Query\Query) {
$query->setStart($event->getOffset())->setRows($event->getLimit());
$solrResult = $client->select($query);
$event->items = $solrResult->getIterator();
$event->count = $solrResult->getNumFound();
$event->setCustomPaginationParameter('result', $solrResult);
$event->stopPropagation();
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 0) /* triggers before a standard array subscriber*/
);
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable;
use Knp\Component\Pager\Event\ItemsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Knp\Component\Pager\PaginatorInterface;
class ArraySubscriber implements EventSubscriberInterface
{
/**
* @var string the field used to sort current object array list
*/
private $currentSortingField;
/**
* @var string the sorting direction
*/
private $sortDirection;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
public function __construct(PropertyAccessorInterface $accessor = null)
{
if (!$accessor && class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) {
$accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
}
$this->propertyAccessor = $accessor;
}
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
if (!is_array($event->target) || empty($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
return;
}
$event->setCustomPaginationParameter('sorted', true);
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST]) && !in_array($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]], $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot sort by: [{$_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]]}] this field is not in whitelist");
}
$sortFunction = isset($event->options['sortFunction']) ? $event->options['sortFunction'] : array($this, 'proxySortFunction');
$sortField = $_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]];
// compatibility layer
if ($sortField[0] === '.') {
$sortField = substr($sortField, 1);
}
call_user_func_array($sortFunction, array(
&$event->target,
$sortField,
isset($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) && strtolower($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) === 'asc' ? 'asc' : 'desc'
));
}
private function proxySortFunction(&$target, $sortField, $sortDirection) {
$this->currentSortingField = $sortField;
$this->sortDirection = $sortDirection;
return usort($target, array($this, 'sortFunction'));
}
/**
* @param mixed $object1 first object to compare
* @param mixed $object2 second object to compare
*
* @return int
*/
private function sortFunction($object1, $object2)
{
if (!$this->propertyAccessor) {
throw new \UnexpectedValueException('You need symfony/property-access component to use this sorting function');
}
try {
$fieldValue1 = $this->propertyAccessor->getValue($object1, $this->currentSortingField);
} catch (UnexpectedTypeException $e) {
return -1 * $this->getSortCoefficient();
}
try {
$fieldValue2 = $this->propertyAccessor->getValue($object2, $this->currentSortingField);
} catch (UnexpectedTypeException $e) {
return 1 * $this->getSortCoefficient();
}
if (is_string($fieldValue1)) {
$fieldValue1 = mb_strtolower($fieldValue1);
}
if (is_string($fieldValue2)) {
$fieldValue2 = mb_strtolower($fieldValue2);
}
if ($fieldValue1 === $fieldValue2) {
return 0;
}
return ($fieldValue1 > $fieldValue2 ? 1 : -1) * $this->getSortCoefficient();
}
private function getSortCoefficient()
{
return $this->sortDirection === 'asc' ? 1 : -1;
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable\Doctrine\ODM\MongoDB;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Doctrine\ODM\MongoDB\Query\Query;
use Knp\Component\Pager\PaginatorInterface;
class QuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
if ($event->target instanceof Query) {
$event->setCustomPaginationParameter('sorted', true);
if (isset($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
$field = $_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]];
$dir = strtolower($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) == 'asc' ? 1 : -1;
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
if (!in_array($field, $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot sort by: [{$field}] this field is not in whitelist");
}
}
static $reflectionProperty;
if (is_null($reflectionProperty)) {
$reflectionClass = new \ReflectionClass('Doctrine\MongoDB\Query\Query');
$reflectionProperty = $reflectionClass->getProperty('query');
$reflectionProperty->setAccessible(true);
}
$queryOptions = $reflectionProperty->getValue($event->target);
//@todo: seems like does not support multisort ??
$queryOptions['sort'] = array($field => $dir);
$reflectionProperty->setValue($event->target, $queryOptions);
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
);
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable\Doctrine\ORM\Query;
use Doctrine\ORM\Query\TreeWalkerAdapter,
Doctrine\ORM\Query\AST\SelectStatement,
Doctrine\ORM\Query\AST\PathExpression,
Doctrine\ORM\Query\AST\OrderByItem,
Doctrine\ORM\Query\AST\OrderByClause;
/**
* OrderBy Query TreeWalker for Sortable functionality
* in doctrine paginator
*/
class OrderByWalker extends TreeWalkerAdapter
{
/**
* Sort key alias hint name
*/
const HINT_PAGINATOR_SORT_ALIAS = 'knp_paginator.sort.alias';
/**
* Sort key field hint name
*/
const HINT_PAGINATOR_SORT_FIELD = 'knp_paginator.sort.field';
/**
* Sort direction hint name
*/
const HINT_PAGINATOR_SORT_DIRECTION = 'knp_paginator.sort.direction';
/**
* Walks down a SelectStatement AST node, modifying it to
* sort the query like requested by url
*
* @param SelectStatement $AST
* @return void
*/
public function walkSelectStatement(SelectStatement $AST)
{
$query = $this->_getQuery();
$fields = (array)$query->getHint(self::HINT_PAGINATOR_SORT_FIELD);
$aliases = (array)$query->getHint(self::HINT_PAGINATOR_SORT_ALIAS);
$components = $this->_getQueryComponents();
foreach ($fields as $index => $field) {
if (!$field) {
continue;
}
$alias = $aliases[$index];
if ($alias !== false) {
if (!array_key_exists($alias, $components)) {
throw new \UnexpectedValueException("There is no component aliased by [{$alias}] in the given Query");
}
$meta = $components[$alias];
if (!$meta['metadata']->hasField($field)) {
throw new \UnexpectedValueException("There is no such field [{$field}] in the given Query component, aliased by [$alias]");
}
} else {
if (!array_key_exists($field, $components)) {
throw new \UnexpectedValueException("There is no component field [{$field}] in the given Query");
}
}
$direction = $query->getHint(self::HINT_PAGINATOR_SORT_DIRECTION);
if ($alias !== false) {
$pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD, $alias, $field);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
} else {
$pathExpression = $field;
}
$orderByItem = new OrderByItem($pathExpression);
$orderByItem->type = $direction;
if ($AST->orderByClause) {
$set = false;
foreach ($AST->orderByClause->orderByItems as $item) {
if ($item->expression instanceof PathExpression) {
if ($item->expression->identificationVariable === $alias && $item->expression->field === $field) {
$item->type = $direction;
$set = true;
break;
}
}
}
if (!$set) {
array_unshift($AST->orderByClause->orderByItems, $orderByItem);
}
} else {
$AST->orderByClause = new OrderByClause(array($orderByItem));
}
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable\Doctrine\ORM;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\Event\Subscriber\Sortable\Doctrine\ORM\Query\OrderByWalker;
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\Helper as QueryHelper;
use Doctrine\ORM\Query;
use Knp\Component\Pager\PaginatorInterface;
class QuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
if ($event->target instanceof Query) {
$event->setCustomPaginationParameter('sorted', true);
if (isset($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
$dir = isset($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) && strtolower($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) === 'asc' ? 'asc' : 'desc';
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
if (!in_array($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]], $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot sort by: [{$_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]]}] this field is not in whitelist");
}
}
$sortFieldParameterNames = $_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]];
$fields = array();
$aliases = array();
foreach (explode('+', $sortFieldParameterNames) as $sortFieldParameterName) {
$parts = explode('.', $sortFieldParameterName, 2);
// We have to prepend the field. Otherwise OrderByWalker will add
// the order-by items in the wrong order
array_unshift($fields, end($parts));
array_unshift($aliases, 2 <= count($parts) ? reset($parts) : false);
}
$event->target
->setHint(OrderByWalker::HINT_PAGINATOR_SORT_DIRECTION, $dir)
->setHint(OrderByWalker::HINT_PAGINATOR_SORT_FIELD, $fields)
->setHint(OrderByWalker::HINT_PAGINATOR_SORT_ALIAS, $aliases)
;
QueryHelper::addCustomTreeWalker($event->target, 'Knp\Component\Pager\Event\Subscriber\Sortable\Doctrine\ORM\Query\OrderByWalker');
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Elastica\Query;
use Elastica\SearchableInterface;
use Knp\Component\Pager\PaginatorInterface;
class ElasticaQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
if (is_array($event->target) && 2 === count($event->target) && reset($event->target) instanceof SearchableInterface && end($event->target) instanceof Query) {
$event->setCustomPaginationParameter('sorted', true);
list($searchable, $query) = $event->target;
if (isset($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
$field = $_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]];
$dir = isset($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) && strtolower($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) === 'asc' ? 'asc' : 'desc';
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST]) && !in_array($field, $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException(sprintf('Cannot sort by: [%s] this field is not in whitelist',$field));
}
$query->setSort(array(
$field => array('order' => $dir),
));
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\ItemsEvent;
class PropelQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
$query = $event->target;
if ($query instanceof \ModelCriteria) {
$event->setCustomPaginationParameter('sorted', true);
if (isset($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
$part = $_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]];
$directionParam = $event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME];
$direction = (isset($_GET[$directionParam]) && strtolower($_GET[$directionParam]) === 'asc')
? 'asc' : 'desc';
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
if (!in_array($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]], $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot sort by: [{$_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]]}] this field is not in whitelist");
}
}
$query->orderBy($part, $direction);
}
}
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable;
use Knp\Component\Pager\Event\ItemsEvent;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Solarium query sorting
*
* @author Marek Kalnik <marekk@theodo.fr>
*/
class SolariumQuerySubscriber implements EventSubscriberInterface
{
public function items(ItemsEvent $event)
{
// Check if the result has already been sorted by an other sort subscriber
$customPaginationParameters = $event->getCustomPaginationParameters();
if (!empty($customPaginationParameters['sorted']) ) {
return;
}
if (is_array($event->target) && 2 == count($event->target)) {
$event->setCustomPaginationParameter('sorted', true);
$values = array_values($event->target);
list($client, $query) = $values;
if ($client instanceof \Solarium\Client && $query instanceof \Solarium\QueryType\Select\Query\Query) {
if (isset($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]])) {
if (isset($event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
if (!in_array($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]], $event->options[PaginatorInterface::SORT_FIELD_WHITELIST])) {
throw new \UnexpectedValueException("Cannot sort by: [{$_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]]}] this field is not in whitelist");
}
}
$query->addSort($_GET[$event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME]], $this->getSortDirection($event));
}
}
}
}
public static function getSubscribedEvents()
{
return array(
// trigger before the pagination subscriber
'knp_pager.items' => array('items', 1),
);
}
private function getSortDirection($event)
{
return isset($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) &&
strtolower($_GET[$event->options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]]) === 'asc' ? 'asc' : 'desc';
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Knp\Component\Pager\Event\Subscriber\Sortable;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\BeforeEvent;
class SortableSubscriber implements EventSubscriberInterface
{
/**
* Lazy-load state tracker
* @var bool
*/
private $isLoaded = false;
public function before(BeforeEvent $event)
{
// Do not lazy-load more than once
if ($this->isLoaded) {
return;
}
$disp = $event->getEventDispatcher();
// hook all standard sortable subscribers
$disp->addSubscriber(new Doctrine\ORM\QuerySubscriber());
$disp->addSubscriber(new Doctrine\ODM\MongoDB\QuerySubscriber());
$disp->addSubscriber(new ElasticaQuerySubscriber());
$disp->addSubscriber(new PropelQuerySubscriber());
$disp->addSubscriber(new SolariumQuerySubscriber());
$disp->addSubscriber(new ArraySubscriber());
$this->isLoaded = true;
}
public static function getSubscribedEvents()
{
return array(
'knp_pager.before' => array('before', 1)
);
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace Knp\Component\Pager\Pagination;
use Countable, Iterator, ArrayAccess;
abstract class AbstractPagination implements PaginationInterface, Countable, Iterator, ArrayAccess
{
protected $currentPageNumber;
protected $numItemsPerPage;
protected $items = array();
protected $totalCount;
protected $paginatorOptions;
protected $customParameters;
/**
* {@inheritDoc}
*/
public function rewind() {
reset($this->items);
}
/**
* {@inheritDoc}
*/
public function current() {
return current($this->items);
}
/**
* {@inheritDoc}
*/
public function key() {
return key($this->items);
}
/**
* {@inheritDoc}
*/
public function next() {
next($this->items);
}
/**
* {@inheritDoc}
*/
public function valid() {
return key($this->items) !== null;
}
/**
* {@inheritDoc}
*/
public function count()
{
return count($this->items);
}
public function setCustomParameters(array $parameters)
{
$this->customParameters = $parameters;
}
public function getCustomParameter($name)
{
return isset($this->customParameters[$name]) ? $this->customParameters[$name] : null;
}
/**
* {@inheritDoc}
*/
public function setCurrentPageNumber($pageNumber)
{
$this->currentPageNumber = $pageNumber;
}
/**
* Get currently used page number
*
* @return integer
*/
public function getCurrentPageNumber()
{
return $this->currentPageNumber;
}
/**
* {@inheritDoc}
*/
public function setItemNumberPerPage($numItemsPerPage)
{
$this->numItemsPerPage = $numItemsPerPage;
}
/**
* Get number of items per page
*
* @return integer
*/
public function getItemNumberPerPage()
{
return $this->numItemsPerPage;
}
/**
* {@inheritDoc}
*/
public function setTotalItemCount($numTotal)
{
$this->totalCount = $numTotal;
}
/**
* Get total item number available
*
* @return integer
*/
public function getTotalItemCount()
{
return $this->totalCount;
}
/**
* {@inheritDoc}
*/
public function setPaginatorOptions($options)
{
$this->paginatorOptions = $options;
}
/**
* Get pagination alias
*
* @return string
*/
public function getPaginatorOption($name)
{
return isset($this->paginatorOptions[$name]) ? $this->paginatorOptions[$name] : null;
}
/**
* {@inheritDoc}
*/
public function setItems($items)
{
if (!is_array($items) && !$items instanceof \Traversable) {
throw new \UnexpectedValueException("Items must be an array type");
}
$this->items = $items;
}
/**
* Get current items
*
* @return array
*/
public function getItems()
{
return $this->items;
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->items);
}
public function offsetGet($offset)
{
return $this->items[$offset];
}
public function offsetSet($offset, $value)
{
if (null === $offset) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->items[$offset]);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Knp\Component\Pager\Pagination;
/**
* Pagination interface strictly defines
* the methods - paginator will use to populate the
* pagination data
*/
interface PaginationInterface
{
/**
* @param integer $pageNumber
*/
function setCurrentPageNumber($pageNumber);
/**
* @param integer $numItemsPerPage
*/
function setItemNumberPerPage($numItemsPerPage);
/**
* @param integer $numTotal
*/
function setTotalItemCount($numTotal);
/**
* @param mixed $items
*/
function setItems($items);
/**
* @param string $options
*/
function setPaginatorOptions($options);
/**
* @param array $parameters
*/
function setCustomParameters(array $parameters);
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Knp\Component\Pager\Pagination;
use Closure;
/**
* @todo: find a way to avoid exposing private member setters
*
* Sliding pagination
*/
class SlidingPagination extends AbstractPagination
{
/**
* Pagination page range
*
* @var integer
*/
private $range = 5;
/**
* Closure which is executed to render pagination
*
* @var Closure
*/
public $renderer;
public function setPageRange($range)
{
$this->range = intval(abs($range));
}
/**
* Renders the pagination
*/
public function __toString()
{
$data = $this->getPaginationData();
$output = '';
if (!$this->renderer instanceof Closure) {
$output = 'override in order to render a template';
} else {
$output = call_user_func($this->renderer, $data);
}
return $output;
}
public function getPaginationData()
{
$pageCount = intval(ceil($this->totalCount / $this->numItemsPerPage));
$current = $this->currentPageNumber;
if ($this->range > $pageCount) {
$this->range = $pageCount;
}
$delta = ceil($this->range / 2);
if ($current - $delta > $pageCount - $this->range) {
$pages = range($pageCount - $this->range + 1, $pageCount);
} else {
if ($current - $delta < 0) {
$delta = $current;
}
$offset = $current - $delta;
$pages = range($offset + 1, $offset + $this->range);
}
$viewData = array(
'last' => $pageCount,
'current' => $current,
'numItemsPerPage' => $this->numItemsPerPage,
'first' => 1,
'pageCount' => $pageCount,
'totalCount' => $this->totalCount,
);
$viewData = array_merge($viewData, $this->paginatorOptions, $this->customParameters);
if ($current - 1 > 0) {
$viewData['previous'] = $current - 1;
}
if ($current + 1 <= $pageCount) {
$viewData['next'] = $current + 1;
}
$viewData['pagesInRange'] = $pages;
$viewData['firstPageInRange'] = min($pages);
$viewData['lastPageInRange'] = max($pages);
if ($this->getItems() !== null) {
$viewData['currentItemCount'] = $this->count();
$viewData['firstItemNumber'] = (($current - 1) * $this->numItemsPerPage) + 1;
$viewData['lastItemNumber'] = $viewData['firstItemNumber'] + $viewData['currentItemCount'] - 1;
}
return $viewData;
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace Knp\Component\Pager;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Knp\Component\Pager\Event\Subscriber\Paginate\PaginationSubscriber;
use Knp\Component\Pager\Event\Subscriber\Sortable\SortableSubscriber;
use Knp\Component\Pager\Event;
/**
* Paginator uses event dispatcher to trigger pagination
* lifecycle events. Subscribers are expected to paginate
* wanted target and finally it generates pagination view
* which is only the result of paginator
*/
class Paginator implements PaginatorInterface
{
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Default options of paginator
*
* @var array
*/
protected $defaultOptions = array(
self::PAGE_PARAMETER_NAME => 'page',
self::SORT_FIELD_PARAMETER_NAME => 'sort',
self::SORT_DIRECTION_PARAMETER_NAME => 'direction',
self::FILTER_FIELD_PARAMETER_NAME => 'filterParam',
self::FILTER_VALUE_PARAMETER_NAME => 'filterValue',
self::DISTINCT => true
);
/**
* Initialize paginator with event dispatcher
* Can be a service in concept. By default it
* hooks standard pagination subscriber
*
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
*/
public function __construct(EventDispatcherInterface $eventDispatcher = null)
{
$this->eventDispatcher = $eventDispatcher;
if (is_null($this->eventDispatcher)) {
$this->eventDispatcher = new EventDispatcher;
$this->eventDispatcher->addSubscriber(new PaginationSubscriber);
$this->eventDispatcher->addSubscriber(new SortableSubscriber);
}
}
/**
* Override the default paginator options
* to be reused for paginations
*
* @param array $options
*/
public function setDefaultPaginatorOptions(array $options)
{
$this->defaultOptions = array_merge($this->defaultOptions, $options);
}
/**
* Paginates anything (depending on event listeners)
* into Pagination object, which is a view targeted
* pagination object (might be aggregated helper object)
* responsible for the pagination result representation
*
* @param mixed $target - anything what needs to be paginated
* @param integer $page - page number, starting from 1
* @param integer $limit - number of items per page
* @param array $options - less used options:
* boolean $distinct - default true for distinction of results
* string $alias - pagination alias, default none
* array $whitelist - sortable whitelist for target fields being paginated
* @throws \LogicException
* @return \Knp\Component\Pager\Pagination\PaginationInterface
*/
public function paginate($target, $page = 1, $limit = 10, array $options = array())
{
$page = (int) $page;
$limit = intval(abs($limit));
if (!$limit) {
throw new \LogicException("Invalid item per page number, must be a positive number");
}
$offset = abs($page - 1) * $limit;
$options = array_merge($this->defaultOptions, $options);
// normalize default sort field
if (isset($options[self::DEFAULT_SORT_FIELD_NAME]) && is_array($options[self::DEFAULT_SORT_FIELD_NAME])) {
$options[self::DEFAULT_SORT_FIELD_NAME] = implode('+', $options[self::DEFAULT_SORT_FIELD_NAME]);
}
// default sort field and direction are set based on options (if available)
if (!isset($_GET[$options[self::SORT_FIELD_PARAMETER_NAME]]) && isset($options[self::DEFAULT_SORT_FIELD_NAME])) {
$_GET[$options[self::SORT_FIELD_PARAMETER_NAME]] = $options[self::DEFAULT_SORT_FIELD_NAME];
if (!isset($_GET[$options[self::SORT_DIRECTION_PARAMETER_NAME]])) {
$_GET[$options[self::SORT_DIRECTION_PARAMETER_NAME]] = isset($options[self::DEFAULT_SORT_DIRECTION]) ? $options[self::DEFAULT_SORT_DIRECTION] : 'asc';
}
}
// before pagination start
$beforeEvent = new Event\BeforeEvent($this->eventDispatcher);
$this->eventDispatcher->dispatch('knp_pager.before', $beforeEvent);
// items
$itemsEvent = new Event\ItemsEvent($offset, $limit);
$itemsEvent->options = &$options;
$itemsEvent->target = &$target;
$this->eventDispatcher->dispatch('knp_pager.items', $itemsEvent);
if (!$itemsEvent->isPropagationStopped()) {
throw new \RuntimeException('One of listeners must count and slice given target');
}
// pagination initialization event
$paginationEvent = new Event\PaginationEvent;
$paginationEvent->target = &$target;
$paginationEvent->options = &$options;
$this->eventDispatcher->dispatch('knp_pager.pagination', $paginationEvent);
if (!$paginationEvent->isPropagationStopped()) {
throw new \RuntimeException('One of listeners must create pagination view');
}
// pagination class can be different, with different rendering methods
$paginationView = $paginationEvent->getPagination();
$paginationView->setCustomParameters($itemsEvent->getCustomPaginationParameters());
$paginationView->setCurrentPageNumber($page);
$paginationView->setItemNumberPerPage($limit);
$paginationView->setTotalItemCount($itemsEvent->count);
$paginationView->setPaginatorOptions($options);
$paginationView->setItems($itemsEvent->items);
// after
$afterEvent = new Event\AfterEvent($paginationView);
$this->eventDispatcher->dispatch('knp_pager.after', $afterEvent);
return $paginationView;
}
/**
* Hooks in the given event subscriber
*
* @param \Symfony\Component\EventDispatcher\EventSubscriberInterface $subscriber
*/
public function subscribe(EventSubscriberInterface $subscriber)
{
$this->eventDispatcher->addSubscriber($subscriber);
}
/**
* Hooks the listener to the given event name
*
* @param string $eventName
* @param object $listener
* @param integer $priority
*/
public function connect($eventName, $listener, $priority = 0)
{
$this->eventDispatcher->addListener($eventName, $listener, $priority);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Knp\Component\Pager;
/**
* PaginatorInterface
*/
interface PaginatorInterface
{
const DEFAULT_SORT_FIELD_NAME = 'defaultSortFieldName';
const DEFAULT_SORT_DIRECTION = 'defaultSortDirection';
const DEFAULT_FILTER_FIELDS = 'defaultFilterFields';
const SORT_FIELD_PARAMETER_NAME = 'sortFieldParameterName';
const SORT_FIELD_WHITELIST = 'sortFieldWhitelist';
const SORT_DIRECTION_PARAMETER_NAME = 'sortDirectionParameterName';
const PAGE_PARAMETER_NAME = 'pageParameterName';
const FILTER_FIELD_PARAMETER_NAME = 'filterFieldParameterName';
const FILTER_VALUE_PARAMETER_NAME = 'filterValueParameterName';
const FILTER_FIELD_WHITELIST = 'filterFieldWhitelist';
const DISTINCT = 'distinct';
/**
* Paginates anything (depending on event listeners)
* into Pagination object, which is a view targeted
* pagination object (might be aggregated helper object)
* responsible for the pagination result representation
*
* @param mixed $target - anything what needs to be paginated
* @param integer $page - page number, starting from 1
* @param integer $limit - number of items per page
* @param array $options - less used options:
* boolean $distinct - default true for distinction of results
* string $alias - pagination alias, default none
* array $whitelist - sortable whitelist for target fields being paginated
* @throws \LogicException
* @return \Knp\Component\Pager\Pagination\PaginationInterface
*/
function paginate($target, $page = 1, $limit = 10, array $options = array());
}