Skip to content

Commit 43862e7

Browse files
committed
Added separate Paginator4 for DBAL 4
1 parent 6014c7b commit 43862e7

9 files changed

Lines changed: 154 additions & 38 deletions

File tree

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ parameters:
3737
- message: '#.*Symfony\\Component\\Routing\\RouteCollectionBuilder.*#'
3838
path: 'tests/Bundle/DataSourceBundle/Fixtures/TestKernel.php'
3939
- message: "#Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata<Tests\\\\FSi\\\\Bundle\\\\DataGridBundle\\\\Fixtures\\\\EntityCategory>::\\$fieldMappings \\(array<string, Doctrine\\\\ORM\\\\Mapping\\\\FieldMapping>\\) does not accept array<string, array<string, array\\|string>>\\.#"
40+
- message: '#Call to an undefined method Doctrine\\DBAL\\Query\\QueryBuilder::\w+\(\).#'
41+
path: 'src/Component/DataSource/Driver/Doctrine/DBAL/Paginator.php'

src/Component/DataSource/Driver/Doctrine/DBAL/DBALDriver.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace FSi\Component\DataSource\Driver\Doctrine\DBAL;
1313

1414
use Closure;
15+
use Composer\InstalledVersions;
16+
use Doctrine\DBAL\Connection;
1517
use Doctrine\DBAL\Query\QueryBuilder;
1618
use FSi\Component\DataSource\Driver\Doctrine\DBAL\Event\PostGetResult;
1719
use FSi\Component\DataSource\Driver\Doctrine\DBAL\Event\PreGetResult;
@@ -23,11 +25,9 @@
2325
use Psr\EventDispatcher\EventDispatcherInterface;
2426

2527
use function sprintf;
26-
use function strpos;
2728

2829
/**
29-
* @template T
30-
* @template-extends AbstractDriver<T>
30+
* @template-extends AbstractDriver<array<string,mixed>>
3131
*/
3232
final class DBALDriver extends AbstractDriver
3333
{
@@ -37,26 +37,30 @@ final class DBALDriver extends AbstractDriver
3737
* @var string|Closure|null
3838
*/
3939
private $indexField;
40+
private ?Connection $connection;
4041

4142
/**
4243
* @param EventDispatcherInterface $eventDispatcher
4344
* @param array<FieldTypeInterface> $fieldTypes
4445
* @param QueryBuilder $queryBuilder
4546
* @param string $alias
4647
* @param string|Closure|null $indexField
48+
* @param Connection|null $connection
4749
*/
4850
public function __construct(
4951
EventDispatcherInterface $eventDispatcher,
5052
array $fieldTypes,
5153
QueryBuilder $queryBuilder,
5254
string $alias,
53-
$indexField = null
55+
string|Closure|null $indexField = null,
56+
?Connection $connection = null
5457
) {
5558
parent::__construct($eventDispatcher, $fieldTypes);
5659

5760
$this->initialQuery = $queryBuilder;
5861
$this->alias = $alias;
5962
$this->indexField = $indexField;
63+
$this->connection = $connection;
6064
}
6165

6266
/**
@@ -67,7 +71,7 @@ public function getQueryFieldName(FieldInterface $field): string
6771
{
6872
$name = $field->getOption('field');
6973

70-
if (true === $field->getOption('auto_alias') && false === strpos($name, ".")) {
74+
if (true === $field->getOption('auto_alias') && false === str_contains($name, ".")) {
7175
$name = "{$this->alias}.{$name}";
7276
}
7377

@@ -103,7 +107,18 @@ public function getResult(array $fields, ?int $first, ?int $max): Result
103107
$query->setFirstResult($first);
104108
}
105109

106-
$result = new Paginator($query);
110+
if (false === InstalledVersions::getVersion('doctrine/dbal') >= '4.0.0') {
111+
$result = new Paginator($query);
112+
} else {
113+
if (null === $this->connection) {
114+
throw new DBALDriverException(
115+
sprintf('Connection is required for "%s" driver.', self::class)
116+
);
117+
}
118+
119+
$result = new Paginator4($this->connection, $query);
120+
}
121+
107122
if (null !== $this->indexField) {
108123
$result = new DBALResult($result, $this->indexField);
109124
}

src/Component/DataSource/Driver/Doctrine/DBAL/DBALFactory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
use function sprintf;
3131

3232
/**
33-
* @template T
34-
* @template-implements DriverFactoryInterface<T>
33+
* @template-implements DriverFactoryInterface<array<string,mixed>>
3534
*/
3635
final class DBALFactory implements DriverFactoryInterface
3736
{
@@ -74,7 +73,8 @@ public function createDriver(array $options = []): DriverInterface
7473
$this->fieldTypes,
7574
$options['qb'],
7675
$options['alias'],
77-
$options['indexField']
76+
$options['indexField'],
77+
$options['connection'],
7878
);
7979
}
8080

src/Component/DataSource/Driver/Doctrine/DBAL/DBALResult.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
namespace FSi\Component\DataSource\Driver\Doctrine\DBAL;
1313

1414
use Closure;
15+
use Countable;
1516
use Doctrine\Common\Collections\ArrayCollection;
1617
use FSi\Component\DataSource\Result;
1718
use InvalidArgumentException;
1819
use RuntimeException;
1920
use Symfony\Component\PropertyAccess\PropertyAccessor;
2021

2122
use function array_key_exists;
22-
use function count;
2323
use function get_class;
2424
use function gettype;
2525
use function is_object;
@@ -28,17 +28,17 @@
2828
/**
2929
* @template T
3030
* @template-implements Result<T>
31-
* @template-extends ArrayCollection<int|string,mixed>
31+
* @template-extends ArrayCollection<int|string,T>
3232
*/
3333
final class DBALResult extends ArrayCollection implements Result
3434
{
3535
private int $count;
3636

3737
/**
38-
* @param Paginator $paginator
38+
* @param Countable&Result<T> $paginator
3939
* @param string|Closure $indexField
4040
*/
41-
public function __construct(Paginator $paginator, $indexField)
41+
public function __construct(Countable&Result $paginator, $indexField)
4242
{
4343
if (false === is_string($indexField) && false === $indexField instanceof Closure) {
4444
throw new InvalidArgumentException(sprintf(
@@ -53,24 +53,22 @@ public function __construct(Paginator $paginator, $indexField)
5353
$data = $paginator->getIterator();
5454

5555
$propertyAccessor = new PropertyAccessor();
56-
if (0 !== count($data)) {
57-
foreach ($data as $element) {
58-
if (true === is_string($indexField)) {
59-
$index = $propertyAccessor->getValue($element, $indexField);
60-
} else {
61-
$index = $indexField($element);
62-
}
63-
64-
if (null === $index) {
65-
throw new RuntimeException('Index cannot be null');
66-
}
56+
foreach ($data as $element) {
57+
if (true === is_string($indexField)) {
58+
$index = $propertyAccessor->getValue($element, $indexField);
59+
} else {
60+
$index = $indexField($element);
61+
}
6762

68-
if (true === array_key_exists($index, $result)) {
69-
throw new RuntimeException("'Duplicate index \"{$index}\"'");
70-
}
63+
if (null === $index) {
64+
throw new RuntimeException('Index cannot be null');
65+
}
7166

72-
$result[$index] = $element;
67+
if (true === array_key_exists($index, $result)) {
68+
throw new RuntimeException("'Duplicate index \"{$index}\"'");
7369
}
70+
71+
$result[$index] = $element;
7472
}
7573

7674
parent::__construct($result);

src/Component/DataSource/Driver/Doctrine/DBAL/Paginator.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
use Doctrine\DBAL\Query\QueryBuilder;
1717
use FSi\Component\DataSource\Result;
1818

19+
use function sprintf;
20+
1921
/**
2022
* @template-implements Result<array<string,mixed>>
23+
* @deprecated since 1.3 and will be removed in 2.0 along with DBAL ^3.0 support
2124
*/
2225
final class Paginator implements Countable, Result
2326
{
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/**
4+
* (c) FSi sp. z o.o. <info@fsi.pl>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace FSi\Component\DataSource\Driver\Doctrine\DBAL;
13+
14+
use ArrayIterator;
15+
use Countable;
16+
use Doctrine\DBAL\Connection;
17+
use Doctrine\DBAL\Query\QueryBuilder;
18+
use FSi\Component\DataSource\Result;
19+
20+
use function sprintf;
21+
22+
/**
23+
* @template-implements Result<array<string,mixed>>
24+
* @internal
25+
*/
26+
final class Paginator4 implements Countable, Result
27+
{
28+
private Connection $connection;
29+
private QueryBuilder $query;
30+
31+
public function __construct(Connection $connection, QueryBuilder $query)
32+
{
33+
$this->connection = $connection;
34+
$this->query = $query;
35+
}
36+
37+
/**
38+
* @return ArrayIterator<int,array<string,mixed>>
39+
*/
40+
public function getIterator(): ArrayIterator
41+
{
42+
$statement = $this->connection->executeQuery(
43+
$this->query->getSQL(),
44+
$this->query->getParameters(),
45+
$this->query->getParameterTypes()
46+
);
47+
48+
return new ArrayIterator($statement->fetchAllAssociative());
49+
}
50+
51+
public function count(): int
52+
{
53+
$sql = (clone $this->query)->setMaxResults(null)->setFirstResult(0)->getSQL();
54+
$query = $this->connection->createQueryBuilder()
55+
->select('COUNT(*)')
56+
->from(sprintf('(%s)', $sql), 'orig_query')
57+
;
58+
59+
$statement = $this->connection->executeQuery(
60+
$query->getSQL(),
61+
$this->query->getParameters(),
62+
$this->query->getParameterTypes()
63+
);
64+
65+
return (int) $statement->fetchOne();
66+
}
67+
}

tests/Component/DataSource/Driver/Doctrine/DBAL/DBALDriverResultTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
namespace Tests\FSi\Component\DataSource\Driver\Doctrine\DBAL;
1313

14+
use Countable;
1415
use Doctrine\DBAL\Connection;
1516
use FSi\Component\DataSource\DataSourceInterface;
16-
use FSi\Component\DataSource\Driver\Doctrine\DBAL\Paginator;
1717
use FSi\Component\DataSource\Extension\Ordering\OrderingExtension;
1818
use FSi\Component\DataSource\Extension\Pagination\PaginationExtension;
19+
use FSi\Component\DataSource\Result;
20+
use Iterator;
1921

2022
use function preg_replace;
2123

@@ -99,10 +101,13 @@ public function testSortingField(): void
99101
'SELECT e.* FROM news e WHERE e.title LIKE :title ORDER BY e.content asc, e.title desc LIMIT 10',
100102
$this->queryLogger->getQueryBuilder()->getSQL()
101103
);
102-
self::assertInstanceOf(Paginator::class, $result);
104+
self::assertInstanceOf(Result::class, $result);
105+
self::assertInstanceOf(Countable::class, $result);
103106
self::assertCount(12, $result);
104107
self::assertCount(10, iterator_to_array($result));
105-
self::assertEquals('title-18', $result->getIterator()->current()['title']);
108+
$iterator = $result->getIterator();
109+
self::assertInstanceOf(Iterator::class, $iterator);
110+
self::assertEquals('title-18', $iterator->current()['title']);
106111
}
107112

108113
/**

tests/Component/DataSource/Driver/Doctrine/DBAL/DBALDriverTest.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ public function testCreation(): void
4848
public function testGetResultExceptionWhenFieldIsNotDBALField(): void
4949
{
5050
$qb = $this->connection->createQueryBuilder();
51-
$driver = new DBALDriver($this->createMock(EventDispatcherInterface::class), [], $qb, 'e');
51+
$driver = new DBALDriver(
52+
$this->createMock(EventDispatcherInterface::class),
53+
[],
54+
$qb,
55+
'e',
56+
null,
57+
$this->connection
58+
);
5259

5360
$this->expectException(DBALDriverException::class);
5461
$fields = [$this->createMock(FieldInterface::class)];
@@ -69,15 +76,22 @@ public function testAllFieldsBuildQueryMethod(): void
6976
}
7077

7178
$qb = $this->connection->createQueryBuilder();
72-
$driver = new DBALDriver($this->createMock(EventDispatcherInterface::class), [], $qb, 'e');
79+
$driver = new DBALDriver(
80+
$this->createMock(EventDispatcherInterface::class),
81+
[],
82+
$qb,
83+
'e',
84+
null,
85+
$this->connection
86+
);
7387
$driver->getResult($fields, 0, 20);
7488
}
7589

7690
public function testExtensionsCalls(): void
7791
{
7892
$qb = $this->connection->createQueryBuilder();
7993
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
80-
$driver = new DBALDriver($eventDispatcher, [], $qb, 'e');
94+
$driver = new DBALDriver($eventDispatcher, [], $qb, 'e', null, $this->connection);
8195

8296
$eventDispatcher->expects(self::exactly(2))
8397
->method('dispatch')
@@ -120,7 +134,9 @@ public function testAllCoreFields(string $type): void
120134
new Time([]),
121135
],
122136
$qb,
123-
'e'
137+
'e',
138+
null,
139+
$this->connection
124140
);
125141
self::assertTrue($driver->hasFieldType($type));
126142
$fieldType = $driver->getFieldType($type);

tests/Component/DataSource/Driver/Doctrine/DBAL/DBALResultTest.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111

1212
namespace Tests\FSi\Component\DataSource\Driver\Doctrine\DBAL;
1313

14+
use Composer\InstalledVersions;
1415
use Doctrine\DBAL\Connection;
1516
use FSi\Component\DataSource\Driver\Doctrine\DBAL\DBALResult;
1617
use FSi\Component\DataSource\Driver\Doctrine\DBAL\Paginator;
18+
use FSi\Component\DataSource\Driver\Doctrine\DBAL\Paginator4;
1719
use RuntimeException;
1820

1921
class DBALResultTest extends TestBase
2022
{
2123
private Connection $connection;
2224

23-
private Paginator $paginator;
25+
private Paginator|Paginator4 $paginator;
2426

2527
protected function setUp(): void
2628
{
@@ -32,7 +34,11 @@ protected function setUp(): void
3234
->from(self::TABLE_CATEGORY_NAME, 'c')
3335
->setMaxResults(3);
3436

35-
$this->paginator = new Paginator($qb);
37+
if (InstalledVersions::getVersion('doctrine/dbal') >= '4.0.0') {
38+
$this->paginator = new Paginator4($this->connection, $qb);
39+
} else {
40+
$this->paginator = new Paginator($qb);
41+
}
3642
}
3743

3844
public function testEmptyResult(): void
@@ -42,7 +48,11 @@ public function testEmptyResult(): void
4248
->from(self::TABLE_CATEGORY_NAME, 'c')
4349
->where('0 = 1');
4450

45-
$paginator = new Paginator($qb);
51+
if (InstalledVersions::getVersion('doctrine/dbal') >= '4.0.0') {
52+
$paginator = new Paginator4($this->connection, $qb);
53+
} else {
54+
$paginator = new Paginator($qb);
55+
}
4656
$result = new DBALResult($paginator, '[id]');
4757

4858
self::assertCount(0, $result);

0 commit comments

Comments
 (0)