diff --git a/src/Pagination/Paginatable.php b/src/Pagination/Paginatable.php index 11cd180f..7e80fc8c 100644 --- a/src/Pagination/Paginatable.php +++ b/src/Pagination/Paginatable.php @@ -2,48 +2,13 @@ namespace LaravelDoctrine\ORM\Pagination; -use Doctrine\ORM\Query; - +/** + * @deprecated Backwards compatibility trait. You should switch to use one of the specific Paginator traits: + * + * @see PaginatesFromRequest + * @see PaginatesFromParams + */ trait Paginatable { - /** - * @param int $perPage - * @param string $pageName - * - * @return \Illuminate\Pagination\LengthAwarePaginator - */ - public function paginateAll($perPage = 15, $pageName = 'page') - { - $query = $this->createQueryBuilder('o')->getQuery(); - - return $this->paginate($query, $perPage, $pageName); - } - - /** - * @param Query $query - * @param int $perPage - * @param bool $fetchJoinCollection - * @param string $pageName - * - * @return \Illuminate\Pagination\LengthAwarePaginator - */ - public function paginate(Query $query, $perPage, $pageName = 'page', $fetchJoinCollection = true) - { - return (new PaginatorAdapter())->make( - $query, - $perPage, - $pageName, - $fetchJoinCollection - ); - } - - /** - * Creates a new QueryBuilder instance that is prepopulated for this entity name. - * - * @param string $alias - * @param string $indexBy The index for the from. - * - * @return \Doctrine\ORM\QueryBuilder - */ - abstract public function createQueryBuilder($alias, $indexBy = null); + use PaginatesFromRequest; } diff --git a/src/Pagination/PaginatesFromParams.php b/src/Pagination/PaginatesFromParams.php new file mode 100644 index 00000000..376ca8c7 --- /dev/null +++ b/src/Pagination/PaginatesFromParams.php @@ -0,0 +1,49 @@ +createQueryBuilder('o')->getQuery(); + + return $this->paginate($query, $perPage, $pageName); + } + + /** + * @param Query $query + * @param int $perPage + * @param int $page + * @param bool $fetchJoinCollection + * + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function paginate(Query $query, $perPage, $page = 1, $fetchJoinCollection = true) + { + return PaginatorAdapter::fromParams( + $query, + $perPage, + $page, + $fetchJoinCollection + )->make(); + } + + /** + * Creates a new QueryBuilder instance that is prepopulated for this entity name. + * + * @param string $alias + * @param string $indexBy The index for the from. + * + * @return \Doctrine\ORM\QueryBuilder + */ + abstract public function createQueryBuilder($alias, $indexBy = null); +} diff --git a/src/Pagination/PaginatesFromRequest.php b/src/Pagination/PaginatesFromRequest.php new file mode 100644 index 00000000..d5c44b1d --- /dev/null +++ b/src/Pagination/PaginatesFromRequest.php @@ -0,0 +1,49 @@ +createQueryBuilder('o')->getQuery(); + + return $this->paginate($query, $perPage, $pageName); + } + + /** + * @param Query $query + * @param int $perPage + * @param bool $fetchJoinCollection + * @param string $pageName + * + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function paginate(Query $query, $perPage, $pageName = 'page', $fetchJoinCollection = true) + { + return PaginatorAdapter::fromRequest( + $query, + $perPage, + $pageName, + $fetchJoinCollection + )->make(); + } + + /** + * Creates a new QueryBuilder instance that is prepopulated for this entity name. + * + * @param string $alias + * @param string $indexBy The index for the from. + * + * @return \Doctrine\ORM\QueryBuilder + */ + abstract public function createQueryBuilder($alias, $indexBy = null); +} diff --git a/src/Pagination/PaginatorAdapter.php b/src/Pagination/PaginatorAdapter.php index 81244545..b98dc8a6 100644 --- a/src/Pagination/PaginatorAdapter.php +++ b/src/Pagination/PaginatorAdapter.php @@ -3,6 +3,7 @@ namespace LaravelDoctrine\ORM\Pagination; use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; @@ -14,24 +15,87 @@ class PaginatorAdapter */ protected $query; + /** + * @var int + */ + private $perPage; + + /** + * @var callable + */ + private $pageResolver; + + /** + * @var bool + */ + private $fetchJoinCollection; + + /** + * @param AbstractQuery $query + * @param int $perPage + * @param callable $pageResolver + * @param bool $fetchJoinCollection + */ + private function __construct(AbstractQuery $query, $perPage, $pageResolver, $fetchJoinCollection) + { + $this->query = $query; + $this->perPage = $perPage; + $this->pageResolver = $pageResolver; + $this->fetchJoinCollection = $fetchJoinCollection; + } + /** * @param AbstractQuery $query * @param int $perPage * @param string $pageName * @param bool $fetchJoinCollection * - * @return LengthAwarePaginator + * @return PaginatorAdapter */ - public function make(AbstractQuery $query, $perPage = 15, $pageName = 'page', $fetchJoinCollection = true) + public static function fromRequest(AbstractQuery $query, $perPage = 15, $pageName = 'page', $fetchJoinCollection = true) { - $this->query($query) - ->skip($this->getSkipAmount($perPage, $pageName)) - ->take($perPage); + return new static( + $query, + $perPage, + function () use ($pageName) { + return Paginator::resolveCurrentPage($pageName); + }, + $fetchJoinCollection + ); + } - return $this->convertToLaravelPaginator( - $this->getDoctrinePaginator($fetchJoinCollection), + /** + * @param AbstractQuery $query + * @param int $perPage + * @param int $page + * @param bool $fetchJoinCollection + * + * @return PaginatorAdapter + */ + public static function fromParams(AbstractQuery $query, $perPage = 15, $page = 1, $fetchJoinCollection = true) + { + return new static( + $query, $perPage, - $pageName + function () use ($page) { + return $page; + }, + $fetchJoinCollection + ); + } + + public function make() + { + $page = $this->getCurrentPage(); + + $this->query($this->query) + ->skip($this->getSkipAmount($this->perPage, $page)) + ->take($this->perPage); + + return $this->convertToLaravelPaginator( + $this->getDoctrinePaginator(), + $this->perPage, + $page ); } @@ -48,7 +112,7 @@ protected function query(AbstractQuery $query) } /** - * @return AbstractQuery + * @return AbstractQuery|Query */ public function getQuery() { @@ -80,74 +144,54 @@ protected function take($perPage) } /** - * @param int $perPage - * @param string $pageName + * @param int $perPage + * @param int $page * * @return int */ - protected function getSkipAmount($perPage, $pageName = 'page') + protected function getSkipAmount($perPage, $page) { - return ($this->getCurrentPage($pageName) - 1) * $perPage; + return ($page - 1) * $perPage; } /** - * @param bool $fetchJoinCollection - * * @return DoctrinePaginator */ - private function getDoctrinePaginator($fetchJoinCollection) + private function getDoctrinePaginator() { return new DoctrinePaginator( $this->getQuery(), - $fetchJoinCollection + $this->fetchJoinCollection ); } /** * @param DoctrinePaginator $doctrinePaginator * @param int $perPage - * @param string $pageName + * @param int $page * * @return LengthAwarePaginator */ - protected function convertToLaravelPaginator(DoctrinePaginator $doctrinePaginator, $perPage, $pageName = 'page') + protected function convertToLaravelPaginator(DoctrinePaginator $doctrinePaginator, $perPage, $page) { - $results = $this->getResults($doctrinePaginator); - $currentPage = $this->getCurrentPage($pageName); + $results = iterator_to_array($doctrinePaginator); $path = Paginator::resolveCurrentPath(); return new LengthAwarePaginator( $results, $doctrinePaginator->count(), $perPage, - $currentPage, + $page, compact('path') ); } /** - * @param DoctrinePaginator $doctrinePaginator - * - * @return array - */ - protected function getResults(DoctrinePaginator $doctrinePaginator) - { - $results = []; - foreach ($doctrinePaginator as $entity) { - $results[] = $entity; - }; - - return $results; - } - - /** - * @param int $pageName - * * @return int */ - protected function getCurrentPage($pageName) + protected function getCurrentPage() { - $page = Paginator::resolveCurrentPage($pageName); + $page = call_user_func($this->pageResolver); return $page > 0 ? $page : 1; } diff --git a/tests/Pagination/PaginatorAdapterTest.php b/tests/Pagination/PaginatorAdapterTest.php new file mode 100644 index 00000000..23f5a18a --- /dev/null +++ b/tests/Pagination/PaginatorAdapterTest.php @@ -0,0 +1,122 @@ +mockEntityManager(); + $query = (new Query($em))->setDQL('SELECT f FROM Foo f'); + $adapter = PaginatorAdapter::fromParams($query, 15, 2); + + $paginator = $adapter->make(); + + $this->assertInstanceOf(LengthAwarePaginator::class, $paginator); + $this->assertEquals(2, $paginator->currentPage()); + } + + public function testMakesLaravelsPaginatorFromRequest() + { + AbstractPaginator::currentPageResolver(function () { + return 13; + }); + + $em = $this->mockEntityManager(); + $query = (new Query($em))->setDQL('SELECT f FROM Foo f'); + $adapter = PaginatorAdapter::fromRequest($query); + + $paginator = $adapter->make(); + + $this->assertInstanceOf(LengthAwarePaginator::class, $paginator); + $this->assertEquals(13, $paginator->currentPage()); + } + + /** + * @return EntityManagerInterface|\Mockery\Mock + */ + private function mockEntityManager() + { + /** @var EntityManagerInterface|\Mockery\Mock $em */ + $em = \Mockery::mock(EntityManagerInterface::class); + $config = \Mockery::mock(Configuration::class); + $metadata = \Mockery::mock(ClassMetadata::class); + $connection = \Mockery::mock(Connection::class); + $platform = \Mockery::mock(AbstractPlatform::class); + $hydrator = \Mockery::mock(AbstractHydrator::class); + + $config->shouldReceive('getDefaultQueryHints')->andReturn([]); + $config->shouldReceive('isSecondLevelCacheEnabled')->andReturn(false); + $config->shouldReceive('getQueryCacheImpl')->andReturn(null); + $config->shouldReceive('getQuoteStrategy')->andReturn(new DefaultQuoteStrategy); + + $metadata->fieldMappings = [ + 'id' => [ + 'fieldName' => 'id', + 'columnName' => 'id', + 'type' => Type::INTEGER, + 'id' => true, + 'options' => ['unsigned' => true], + ], + 'name' => [ + 'fieldName' => 'name', + 'columnName' => 'name', + 'type' => Type::STRING, + ], + ]; + + $metadata->subClasses = []; + $metadata->name = 'Foo'; + $metadata->containsForeignIdentifier = false; + $metadata->identifier = ['id']; + + $metadata->shouldReceive('isInheritanceTypeSingleTable')->andReturn(false); + $metadata->shouldReceive('isInheritanceTypeJoined')->andReturn(false); + $metadata->shouldReceive('getTableName')->andReturn('fooes'); + $metadata->shouldReceive('getTypeOfField')->andReturn(Type::INTEGER); + + $connection->shouldReceive('getDatabasePlatform')->andReturn($platform); + $connection->shouldReceive('executeQuery')->andReturn([]); + + $platform->shouldReceive('appendLockHint')->andReturnUsing(function ($a) { + return $a; + }); + $platform->shouldReceive('getMaxIdentifierLength')->andReturn(PHP_INT_MAX); + $platform->shouldReceive('getSQLResultCasing')->andReturnUsing(function ($a) { + return $a; + }); + $platform->shouldReceive('getName')->andReturn('You shouldnt care'); + $platform->shouldReceive('getCountExpression')->andReturnUsing(function ($col) { + return "COUNT($col)"; + }); + $platform->shouldReceive('supportsLimitOffset')->andReturn(true); + + $hydrator->shouldReceive('hydrateAll')->andReturn([]); + + $em->shouldReceive('getConfiguration')->andReturn($config); + $em->shouldReceive('getClassMetadata')->with('Foo')->andReturn($metadata); + $em->shouldReceive('getConnection')->andReturn($connection); + $em->shouldReceive('hasFilters')->andReturn(false); + $em->shouldReceive('newHydrator')->andReturn($hydrator); + + return $em; + } +} + +class Foo +{ + private $id; + + private $name; +}