Skip to content

Commit c9485d5

Browse files
authored
extend compatibility for doctrine/orm 2.19 (#8)
1 parent aad1f02 commit c9485d5

File tree

5 files changed

+62
-42
lines changed

5 files changed

+62
-42
lines changed

.github/workflows/checks.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
fail-fast: false
3434
matrix:
3535
php-version: [ '8.1', '8.2', '8.3' ]
36+
doctrine-version: [ '^2.19', '^3.2' ]
3637
dependency-version: [ prefer-lowest, prefer-stable ]
3738
steps:
3839
-
@@ -44,8 +45,11 @@ jobs:
4445
with:
4546
php-version: ${{ matrix.php-version }}
4647
-
47-
name: Update dependencies
48+
name: Install dependencies
4849
run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction
50+
-
51+
name: Install correct version of Doctrine
52+
run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction --with-all-dependencies doctrine/orm:${{ matrix.doctrine-version }}
4953
-
5054
name: Run tests
5155
run: composer check:tests

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
],
77
"require": {
88
"php": "^8.1",
9-
"doctrine/orm": "^3.2"
9+
"doctrine/orm": "^2.19.7 || ^3.2"
1010
},
1111
"require-dev": {
1212
"doctrine/collections": "^2.2",
13-
"doctrine/dbal": "^4",
13+
"doctrine/dbal": "^3.9 || ^4.0",
14+
"doctrine/persistence": "^3.3",
1415
"editorconfig-checker/editorconfig-checker": "^10.6.0",
1516
"ergebnis/composer-normalize": "^2.42.0",
1617
"nette/utils": "^4",

phpstan.neon.dist

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ parameters:
2424
- ShipMonk\DoctrineEntityPreloader\Exception\RuntimeException
2525

2626
ignoreErrors:
27+
-
28+
message: '#Strict comparison using === between ReflectionProperty and null will always evaluate to false#'
29+
identifier: 'identical.alwaysFalse'
30+
reportUnmatched: false
31+
path: 'src/EntityPreloader.php'
32+
-
33+
message: '#Result of \|\| is always false#'
34+
identifier: 'booleanOr.alwaysFalse'
35+
reportUnmatched: false
36+
path: 'src/EntityPreloader.php'
2737
-
2838
message: '#has an uninitialized property \$id#'
2939
identifier: 'property.uninitialized'
@@ -39,4 +49,5 @@ parameters:
3949
path: 'tests/Fixtures/Synthetic'
4050
-
4151
identifier: 'property.unusedType'
52+
reportUnmatched: false
4253
path: 'tests/Fixtures/Synthetic'

src/EntityPreloader.php

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
namespace ShipMonk\DoctrineEntityPreloader;
44

5+
use ArrayAccess;
56
use Doctrine\ORM\EntityManagerInterface;
67
use Doctrine\ORM\Mapping\ClassMetadata;
7-
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
8-
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
9-
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
108
use Doctrine\ORM\PersistentCollection;
119
use Doctrine\ORM\QueryBuilder;
1210
use LogicException;
@@ -56,19 +54,19 @@ public function preload(
5654
$associationMapping = $sourceClassMetadata->getAssociationMapping($sourcePropertyName);
5755

5856
/** @var ClassMetadata<E> $targetClassMetadata */
59-
$targetClassMetadata = $this->entityManager->getClassMetadata($associationMapping->targetEntity);
57+
$targetClassMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']);
6058

61-
if ($associationMapping->isIndexed()) {
59+
if (isset($associationMapping['indexBy'])) {
6260
throw new LogicException('Preloading of indexed associations is not supported');
6361
}
6462

6563
$maxFetchJoinSameFieldCount ??= 1;
6664
$sourceEntities = $this->loadProxies($sourceClassMetadata, $sourceEntities, $batchSize ?? self::PRELOAD_ENTITY_DEFAULT_BATCH_SIZE, $maxFetchJoinSameFieldCount);
6765

68-
$preloader = match (true) {
69-
$associationMapping->isToOne() => $this->preloadToOne(...),
70-
$associationMapping->isToMany() => $this->preloadToMany(...),
71-
default => throw new LogicException("Unsupported association mapping type {$associationMapping->type()}"),
66+
$preloader = match ($associationMapping['type']) {
67+
ClassMetadata::ONE_TO_ONE, ClassMetadata::MANY_TO_ONE => $this->preloadToOne(...),
68+
ClassMetadata::ONE_TO_MANY, ClassMetadata::MANY_TO_MANY => $this->preloadToMany(...),
69+
default => throw new LogicException("Unsupported association mapping type {$associationMapping['type']}"),
7270
};
7371

7472
return $preloader($sourceEntities, $sourceClassMetadata, $sourcePropertyName, $targetClassMetadata, $batchSize, $maxFetchJoinSameFieldCount);
@@ -201,13 +199,9 @@ private function preloadToMany(
201199

202200
$associationMapping = $sourceClassMetadata->getAssociationMapping($sourcePropertyName);
203201

204-
if (!$associationMapping instanceof ToManyAssociationMapping) {
205-
throw new LogicException('Unsupported association mapping type');
206-
}
207-
208-
$innerLoader = match (true) {
209-
$associationMapping instanceof OneToManyAssociationMapping => $this->preloadOneToManyInner(...),
210-
$associationMapping instanceof ManyToManyAssociationMapping => $this->preloadManyToManyInner(...),
202+
$innerLoader = match ($associationMapping['type']) {
203+
ClassMetadata::ONE_TO_MANY => $this->preloadOneToManyInner(...),
204+
ClassMetadata::MANY_TO_MANY => $this->preloadManyToManyInner(...),
211205
default => throw new LogicException('Unsupported association mapping type'),
212206
};
213207

@@ -238,6 +232,7 @@ private function preloadToMany(
238232
}
239233

240234
/**
235+
* @param array<string, mixed>|ArrayAccess<string, mixed> $associationMapping
241236
* @param ClassMetadata<S> $sourceClassMetadata
242237
* @param ClassMetadata<T> $targetClassMetadata
243238
* @param list<mixed> $uninitializedSourceEntityIdsChunk
@@ -248,7 +243,7 @@ private function preloadToMany(
248243
* @template T of E
249244
*/
250245
private function preloadOneToManyInner(
251-
ToManyAssociationMapping $associationMapping,
246+
array|ArrayAccess $associationMapping,
252247
ClassMetadata $sourceClassMetadata,
253248
ReflectionProperty $sourceIdentifierReflection,
254249
string $sourcePropertyName,
@@ -272,7 +267,7 @@ private function preloadOneToManyInner(
272267
$targetPropertyName,
273268
$uninitializedSourceEntityIdsChunk,
274269
$maxFetchJoinSameFieldCount,
275-
$associationMapping->orderBy(),
270+
$associationMapping['orderBy'] ?? [],
276271
);
277272

278273
foreach ($targetEntitiesList as $targetEntity) {
@@ -288,6 +283,7 @@ private function preloadOneToManyInner(
288283
}
289284

290285
/**
286+
* @param array<string, mixed>|ArrayAccess<string, mixed> $associationMapping
291287
* @param ClassMetadata<S> $sourceClassMetadata
292288
* @param ClassMetadata<T> $targetClassMetadata
293289
* @param list<mixed> $uninitializedSourceEntityIdsChunk
@@ -298,7 +294,7 @@ private function preloadOneToManyInner(
298294
* @template T of E
299295
*/
300296
private function preloadManyToManyInner(
301-
ToManyAssociationMapping $associationMapping,
297+
array|ArrayAccess $associationMapping,
302298
ClassMetadata $sourceClassMetadata,
303299
ReflectionProperty $sourceIdentifierReflection,
304300
string $sourcePropertyName,
@@ -309,7 +305,7 @@ private function preloadManyToManyInner(
309305
int $maxFetchJoinSameFieldCount,
310306
): array
311307
{
312-
if (count($associationMapping->orderBy()) > 0) {
308+
if (count($associationMapping['orderBy'] ?? []) > 0) {
313309
throw new LogicException('Many-to-many associations with order by are not supported');
314310
}
315311

@@ -458,11 +454,11 @@ private function addFetchJoinsToPreventFetchDuringHydration(
458454
}
459455

460456
/** @var ClassMetadata<E> $targetClassMetadata */
461-
$targetClassMetadata = $this->entityManager->getClassMetadata($associationMapping->targetEntity);
457+
$targetClassMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']);
462458

463-
$isToOne = ($associationMapping->type() & ClassMetadata::TO_ONE) !== 0;
464-
$isToOneInversed = $isToOne && !$associationMapping->isOwningSide();
465-
$isToOneAbstract = $isToOne && $associationMapping->isOwningSide() && count($targetClassMetadata->subClasses) > 0;
459+
$isToOne = ($associationMapping['type'] & ClassMetadata::TO_ONE) !== 0;
460+
$isToOneInversed = $isToOne && $associationMapping['isOwningSide'] === false;
461+
$isToOneAbstract = $isToOne && $associationMapping['isOwningSide'] === true && count($targetClassMetadata->subClasses) > 0;
466462

467463
if (!$isToOneInversed && !$isToOneAbstract) {
468464
continue;

tests/EntityPreloadBlogOneHasManyTest.php

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
namespace ShipMonkTests\DoctrineEntityPreloader;
44

5+
use Composer\InstalledVersions;
56
use Doctrine\ORM\Mapping\ClassMetadata;
67
use Doctrine\ORM\Query\QueryException;
78
use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Article;
89
use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Category;
910
use ShipMonkTests\DoctrineEntityPreloader\Lib\TestCase;
11+
use function str_starts_with;
1012

1113
class EntityPreloadBlogOneHasManyTest extends TestCase
1214
{
@@ -54,21 +56,27 @@ public function testOneHasManyWithWithManualPreloadUsingPartial(): void
5456

5557
$categories = $this->getEntityManager()->getRepository(Category::class)->findAll();
5658

57-
// partial no longer works in doctrine 3.0
58-
self::assertException(
59-
QueryException::class,
60-
null,
61-
function () use ($categories): void {
62-
$this->getEntityManager()->createQueryBuilder()
63-
->select('PARTIAL category.{id}', 'article')
64-
->from(Category::class, 'category')
65-
->leftJoin('category.articles', 'article')
66-
->where('category IN (:categories)')
67-
->setParameter('categories', $categories)
68-
->getQuery()
69-
->getResult();
70-
},
71-
);
59+
$query = $this->getEntityManager()->createQueryBuilder()
60+
->select('PARTIAL category.{id}', 'article')
61+
->from(Category::class, 'category')
62+
->leftJoin('category.articles', 'article')
63+
->where('category IN (:categories)')
64+
->setParameter('categories', $categories)
65+
->getQuery();
66+
67+
if (str_starts_with(InstalledVersions::getVersion('doctrine/orm') ?? 'unknown', '3.')) {
68+
self::assertException(QueryException::class, null, static fn() => $query->getResult());
69+
70+
} else {
71+
$query->getResult();
72+
73+
$this->readArticleTitles($categories);
74+
75+
self::assertAggregatedQueries([
76+
['count' => 1, 'query' => 'SELECT * FROM category t0'],
77+
['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN article a1_ ON c0_.id = a1_.category_id WHERE c0_.id IN (?, ?, ?, ?, ?)'],
78+
]);
79+
}
7280
}
7381

7482
public function testOneHasManyWithFetchJoin(): void

0 commit comments

Comments
 (0)