Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pager] Doctrine Bridge #175

Merged
merged 5 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions docs/components/pager/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@ if ($pager->hasNextPage()) {
}
```

## Adapters
## Custom Adapters

Creating Custom Adapters is easy. You can take a look at the available adapters
to see how easy it is.

Please see the [Pager Contract](../../contracts/pager/index.md) to learn more.

## Available Adapters

### ArrayAdapter

Expand Down Expand Up @@ -97,10 +104,9 @@ $adapter = new CallableAdapter(
);
```

### ArrayCollectionAdapter

### ArrayCollectionAdapter (doctrine/collections)

!!! warning
!!! warning "Requires `sonsofphp/pager-doctrine-collections`"
```shell
composer require sonsofphp/pager-doctrine-collections
```
Expand All @@ -115,3 +121,41 @@ $collection = new ArrayCollection();

$adapter = new ArrayCollectionAdapter($collection);
```

### QueryBuilderAdapter (doctrine/dbal)

!!! warning "Requires `sonsofphp/pager-doctrine-dbal`"
```shell
composer require sonsofphp/pager-doctrine-dbal
```

```php
<?php

use Doctrine\DBAL\Query\QueryBuilder;
use SonsOfPHP\Bridge\Doctrine\DBAL\Pager\QueryBuilderAdapter;

// ...

$adapter = new QueryBuilderAdapter($builder, function (QueryBuilder $builder): void {
$builder->select('COUNT(e.id) as total');
});
```

### QueryBuilderAdapter (doctrine/orm)

!!! warning "Requires `sonsofphp/pager-doctrine-orm`"
```shell
composer require sonsofphp/pager-doctrine-orm
```

```php
<?php

use Doctrine\ORM\QueryBuilder;
use SonsOfPHP\Bridge\Doctrine\ORM\Pager\QueryBuilderAdapter;

$builder = $repository->createQueryBuilder('e');

$adapter = new QueryBuilderAdapter($builder);
```
34 changes: 34 additions & 0 deletions docs/contracts/pager/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,37 @@ components.
```shell
composer require sonsofphp/pager-contract
```

## AdapterInterface

```php
<?php

namespace SonsOfPHP\Contract\Pager;

interface AdapterInterface extends \Countable
{
/**
* This will return part of the total results
*
* Offset is where the results to be returned will start, for example, if
* offset is 0, it will start with the first record and return $length
*
* Offset should always be 0 or greater
*
* Length is how many results to return. For example, if length is 10, a
* MAXIMUM of 10 results will be returned
*
* Length MUST always be a positive number that is 1 or greater OR be null
*
* If null is passed in as length, this should return ALL the results.
*
* If the total number of results is less than length, an exception must
* not be thrown.
*
* @throws \InvalidArgumentException
* If offset or length is invalid, this expection will be thrown
*/
public function getSlice(int $offset, ?int $length): iterable;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Bridge\Doctrine\Collections\Pager\Tests;

use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;
use SonsOfPHP\Bridge\Doctrine\Collections\Pager\ArrayCollectionAdapter;
use SonsOfPHP\Contract\Pager\AdapterInterface;

/**
* @coversDefaultClass \SonsOfPHP\Bridge\Doctrine\Collections\Pager\ArrayCollectionAdapter
*
* @uses \SonsOfPHP\Bridge\Doctrine\Collections\Pager\ArrayCollectionAdapter
*/
final class ArrayCollectionAdapterTest extends TestCase
{
/**
* @covers ::__construct
*/
public function testItHasTheRightInterface(): void
{
$adapter = new ArrayCollectionAdapter(new ArrayCollection());

$this->assertInstanceOf(AdapterInterface::class, $adapter);
}

/**
* @covers ::count
*/
public function testCount(): void
{
$adapter = new ArrayCollectionAdapter(new ArrayCollection(range(0, 9)));

$this->assertCount(10, $adapter);
}

/**
* @covers ::getSlice
*/
public function testGetSlice(): void
{
$adapter = new ArrayCollectionAdapter(new ArrayCollection(range(0, 9)));

$this->assertSame([0], $adapter->getSlice(0, 1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,50 @@
use SonsOfPHP\Contract\Pager\AdapterInterface;

/**
* Usage:
* $adapter = new QueryBuilderAdapter($queryBuilder, function (QueryBuilder $builder): void {
* $builder->select('COUNT(DISTINCT e.id) AS cnt');
* });
*
* @author Joshua Estes <[email protected]>
*/
class QueryBuilderAdapter implements AdapterInterface
{
private $countQuery;

public function __construct(
private QueryBuilder $builder,
) {}
private readonly QueryBuilder $builder,
callable $countQuery,
) {
$this->countQuery = $countQuery;
}

/**
* {@inheritdoc}
*/
public function count(): int
{
throw new \Exception('@todo');
$builder = clone $this->builder;
$callable = $this->countQuery;

$callable($builder);
$builder->setMaxResults(1);

return (int) $builder->executeQuery()->fetchOne();
}

/**
* {@inheritdoc}
*/
public function getSlice(int $offset, ?int $length): iterable
{
throw new \Exception('@todo');
$builder = clone $this->builder;

return $builder
->setFirstResult($offset)
->setMaxResults($length)
->executeQuery()
->fetchAllAssociative()
;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Bridge\Doctrine\DBAL\Pager\Tests;

use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Result;
use PHPUnit\Framework\TestCase;
use SonsOfPHP\Bridge\Doctrine\DBAL\Pager\QueryBuilderAdapter;
use SonsOfPHP\Contract\Pager\AdapterInterface;

/**
* @coversDefaultClass \SonsOfPHP\Bridge\Doctrine\DBAL\Pager\QueryBuilderAdapter
*
* @uses \SonsOfPHP\Bridge\Doctrine\DBAL\Pager\QueryBuilderAdapter
*/
final class QueryBuilderAdapterTest extends TestCase
{
private $builder;
private $result;

public function setUp(): void
{
$this->builder = $this->createMock(QueryBuilder::class);
$this->result = $this->createMock(Result::class);
}

/**
* @covers ::__construct
*/
public function testItHasTheRightInterface(): void
{
$adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void {});

$this->assertInstanceOf(AdapterInterface::class, $adapter);
}

/**
* @covers ::count
*/
public function testCount(): void
{
$this->builder->method('executeQuery')->willReturn($this->result);
$this->builder->expects($this->once())->method('select');

$this->result->method('fetchOne')->willReturn(123);

$adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void {
$builder->select('COUNT()');
});

$this->assertCount(123, $adapter);
}

/**
* @covers ::getSlice
*/
public function testSlice(): void
{
$this->builder
->expects($this->once())
->method('setFirstResult')
->with($this->identicalTo(0))
->willReturn($this->builder)
;
$this->builder
->expects($this->once())
->method('setMaxResults')
->with($this->identicalTo(100))
->willReturn($this->builder)
;

$adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void {});

$adapter->getSlice(0, 100);
}
}
45 changes: 45 additions & 0 deletions src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/QueryBuilderAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Bridge\Doctrine\ORM\Pager;

use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use SonsOfPHP\Contract\Pager\AdapterInterface;

/**
* @author Joshua Estes <[email protected]>
*/
class QueryBuilderAdapter implements AdapterInterface
{
private readonly Paginator $paginator;

public function __construct(
QueryBuilder $builder,
) {
$this->paginator = new Paginator($builder, fetchJoinCollection: true);
}

/**
* {@inheritdoc}
*/
public function count(): int
{
return count($this->paginator);
}

/**
* {@inheritdoc}
*/
public function getSlice(int $offset, ?int $length): iterable
{
$query = $this->paginator->getQuery();
$query
->setFirstResult($offset)
->setMaxResults($length)
;

return $this->paginator->getIterator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Bridge\Doctrine\ORM\Pager\Tests;

use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase;
use SonsOfPHP\Bridge\Doctrine\ORM\Pager\QueryBuilderAdapter;
use SonsOfPHP\Contract\Pager\AdapterInterface;

/**
* @coversDefaultClass \SonsOfPHP\Bridge\Doctrine\ORM\Pager\QueryBuilderAdapter
*
* @uses \SonsOfPHP\Bridge\Doctrine\ORM\Pager\QueryBuilderAdapter
*/
final class QueryBuilderAdapterTest extends TestCase
{
private $builder;

public function setUp(): void
{
$this->builder = $this->createMock(QueryBuilder::class);
}

/**
* @covers ::__construct
*/
public function testItHasTheRightInterface(): void
{
$adapter = new QueryBuilderAdapter($this->builder);

$this->assertInstanceOf(AdapterInterface::class, $adapter);
}
}
2 changes: 1 addition & 1 deletion src/SonsOfPHP/Contract/Pager/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface AdapterInterface extends \Countable
* Length is how many results to return. For example, if length is 10, a
* MAXIMUM of 10 results will be returned
*
* Length should always be a positive number that is 1 or greater
* Length MUST always be a positive number that is 1 or greater OR be null
*
* If null is passed in as length, this should return ALL the results.
*
Expand Down
Loading