forked from BabDev/Pagerfanta
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
src/Pagerfanta/Adapter/DoctrineORMNativeQueryAdapter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Pagerfanta package. | ||
* | ||
* (c) Pablo Díez <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Pagerfanta\Adapter; | ||
|
||
use Doctrine\ORM\NativeQuery; | ||
use Doctrine\ORM\Query\ResultSetMapping; | ||
use Pagerfanta\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* @author Maxime Veber <[email protected]> | ||
*/ | ||
class DoctrineORMNativeQueryAdapter implements AdapterInterface | ||
{ | ||
/** | ||
* @var NativeQuery | ||
*/ | ||
private $nativeQuery; | ||
|
||
/** | ||
* @var callable | ||
*/ | ||
private $countQueryBuilderModifier; | ||
|
||
/** | ||
* @var callable | ||
*/ | ||
private $sliceQueryBuilderModifier; | ||
|
||
/** | ||
* @param NativeQuery $query A DBAL query builder. | ||
* @param callable $countQueryBuilderModifier A callable to modify the query to count. | ||
* @throws InvalidArgumentException | ||
*/ | ||
public function __construct(NativeQuery $query, $countQueryBuilderModifier = null, $sliceQueryBuilderModifier = null) | ||
{ | ||
if (strpos(strtolower($query->getSQL()), 'select') !== 0) { | ||
throw new InvalidArgumentException('Only SELECT queries can be paginated.'); | ||
} | ||
|
||
if ($countQueryBuilderModifier !== null && !is_callable($countQueryBuilderModifier)) { | ||
throw new InvalidArgumentException('The count query builder modifier must be a callable.'); | ||
} | ||
if ($sliceQueryBuilderModifier !== null && !is_callable($sliceQueryBuilderModifier)) { | ||
throw new InvalidArgumentException('The slice query builder modifier must be a callable.'); | ||
} | ||
|
||
$this->nativeQuery = $query; | ||
$this->countQueryBuilderModifier = $countQueryBuilderModifier; | ||
$this->sliceQueryBuilderModifier = $sliceQueryBuilderModifier; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getNbResults() | ||
{ | ||
$query = $this->cloneQuery(); | ||
$this->countQueryBuilder($query); | ||
|
||
if ($this->countQueryBuilderModifier !== null) { | ||
call_user_func($this->countQueryBuilderModifier, $query); | ||
} | ||
|
||
$res = $query->getScalarResult(); | ||
|
||
return $res[0]['res']; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getSlice($offset, $length) | ||
{ | ||
$query = $this->cloneQuery(); | ||
|
||
if ($this->countQueryBuilderModifier !== null) { | ||
call_user_func($this->sliceQueryBuilderModifier, $query, $offset, $length); | ||
} else { | ||
$this->sliceQueryBuilder($query, $offset, $length); | ||
} | ||
|
||
return $query->getResult(); | ||
} | ||
|
||
private function cloneQuery() | ||
{ | ||
$query = clone $this->nativeQuery; | ||
$query->setParameters($this->nativeQuery->getParameters()); | ||
|
||
return $query; | ||
} | ||
|
||
private function countQueryBuilder(NativeQuery $query) | ||
{ | ||
$sql = explode(' FROM ', $query->getSql()); | ||
$sql[0] = 'SELECT COUNT(*) AS res'; | ||
$sql = implode(' FROM ', $sql); | ||
|
||
$rsm = new ResultSetMapping(); | ||
$rsm->addScalarResult('res', 'res'); | ||
|
||
$query->setResultSetMapping($rsm); | ||
$query->setSQL($sql); | ||
} | ||
|
||
private function sliceQueryBuilder(NativeQuery $query, $offset, $length) | ||
{ | ||
$sql = $query->getSql(); | ||
$sql .= ' LIMIT ' . $offset . ', '. $length; | ||
$query->setSQL($sql); | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
tests/Pagerfanta/Tests/Adapter/DoctrineORMNativeQueryAdapterTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?php | ||
|
||
namespace Pagerfanta\Tests\Adapter; | ||
|
||
use Doctrine\ORM\NativeQuery; | ||
use Doctrine\ORM\Query\ResultSetMappingBuilder; | ||
use Doctrine\ORM\Tools\SchemaTool; | ||
use Pagerfanta\Adapter\DoctrineORMNativeQueryAdapter; | ||
use Pagerfanta\Tests\Adapter\DoctrineORM\DoctrineORMTestCase; | ||
use Pagerfanta\Tests\Adapter\DoctrineORM\Group; | ||
use Pagerfanta\Tests\Adapter\DoctrineORM\Person; | ||
use Pagerfanta\Tests\Adapter\DoctrineORM\User; | ||
|
||
class DoctrineORMNativeQueryAdapterTest extends DoctrineORMTestCase | ||
{ | ||
private $user1; | ||
private $user2; | ||
|
||
public function setUp() | ||
{ | ||
parent::setUp(); | ||
|
||
$schemaTool = new SchemaTool($this->entityManager); | ||
$schemaTool->createSchema([ | ||
$this->entityManager->getClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\User'), | ||
$this->entityManager->getClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\Group'), | ||
$this->entityManager->getClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\Person'), | ||
]); | ||
|
||
$this->user1 = $user = new User(); | ||
$this->user2 = $user2 = new User(); | ||
$group1 = new Group(); | ||
$group2 = new Group(); | ||
$group3 = new Group(); | ||
$user->groups[] = $group1; | ||
$user->groups[] = $group2; | ||
$user->groups[] = $group3; | ||
$user2->groups[] = $group1; | ||
$author1 = new Person(); | ||
$author1->name = 'Foo'; | ||
$author1->biography = 'Baz bar'; | ||
$author2 = new Person(); | ||
$author2->name = 'Bar'; | ||
$author2->biography = 'Bar baz'; | ||
|
||
$this->entityManager->persist($user); | ||
$this->entityManager->persist($user2); | ||
$this->entityManager->persist($group1); | ||
$this->entityManager->persist($group2); | ||
$this->entityManager->persist($group3); | ||
$this->entityManager->persist($author1); | ||
$this->entityManager->persist($author2); | ||
$this->entityManager->flush(); | ||
} | ||
|
||
public function testAdapterCount() | ||
{ | ||
$sql = "SELECT * FROM User u"; | ||
|
||
$rsm = new ResultSetMappingBuilder($this->entityManager); | ||
$rsm->addRootEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\User', 'u'); | ||
|
||
$nq = $this->entityManager->createNativeQuery($sql, $rsm); | ||
|
||
$adapter = new DoctrineORMNativeQueryAdapter($nq); | ||
$this->assertEquals(2, $adapter->getNbResults()); | ||
} | ||
|
||
public function testAdapterCountFetchJoin() | ||
{ | ||
$count = function (NativeQuery $query) { | ||
$query->setSQL('SELECT COUNT(*) AS res FROM User u'); | ||
}; | ||
|
||
$rsm = new ResultSetMappingBuilder($this->entityManager); | ||
$rsm->addRootEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\User', 'u'); | ||
$rsm->addJoinedEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\Group', 'g', 'u', 'groups', ['id' => 'user_id']); | ||
$sql = "SELECT " . $rsm->generateSelectClause(['u' => 'u', 'g' => 'g']) . | ||
" FROM User u INNER JOIN user_group ug ON u.id = ug.user_id LEFT JOIN groups g ON g.id = ug.group_id" | ||
; | ||
|
||
$nq = $this->entityManager->createNativeQuery($sql, $rsm); | ||
$adapter = new DoctrineORMNativeQueryAdapter($nq, $count); | ||
$this->assertEquals(2, $adapter->getNbResults()); | ||
} | ||
|
||
public function testGetSlice() | ||
{ | ||
$sql = "SELECT * FROM User u"; | ||
|
||
$rsm = new ResultSetMappingBuilder($this->entityManager); | ||
$rsm->addRootEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\User', 'u'); | ||
|
||
$nq = $this->entityManager->createNativeQuery($sql, $rsm); | ||
|
||
$adapter = new DoctrineORMNativeQueryAdapter($nq); | ||
$this->assertEquals(1, count( $adapter->getSlice(0, 1)) ); | ||
$this->assertEquals(2, count( $adapter->getSlice(0, 10)) ); | ||
$this->assertEquals(1, count( $adapter->getSlice(1, 1)) ); | ||
} | ||
|
||
public function testGetSliceFetchJoin() | ||
{ | ||
$rsm = new ResultSetMappingBuilder($this->entityManager); | ||
$rsm->addRootEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\User', 'u'); | ||
$rsm->addJoinedEntityFromClassMetadata('Pagerfanta\Tests\Adapter\DoctrineORM\Group', 'g', 'u', 'groups', ['id' => 'user_id']); | ||
$sql = "SELECT " . $rsm->generateSelectClause(['u' => 'u', 'g' => 'g']) . | ||
" FROM User u INNER JOIN user_group ug ON u.id = ug.user_id LEFT JOIN groups g ON g.id = ug.group_id" | ||
; | ||
|
||
$nq = $this->entityManager->createNativeQuery($sql, $rsm); | ||
$adapter = new DoctrineORMNativeQueryAdapter($nq); | ||
$this->assertEquals(1, count( $adapter->getSlice(0, 1)) ); | ||
$this->assertEquals(2, count( $adapter->getSlice(0, 10)) ); | ||
$this->assertEquals(1, count( $adapter->getSlice(1, 1)) ); | ||
} | ||
} |