Skip to content

Commit

Permalink
extend compatibility for doctrine/orm 2.19 (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
JanTvrdik authored Oct 10, 2024
1 parent aad1f02 commit c9485d5
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
fail-fast: false
matrix:
php-version: [ '8.1', '8.2', '8.3' ]
doctrine-version: [ '^2.19', '^3.2' ]
dependency-version: [ prefer-lowest, prefer-stable ]
steps:
-
Expand All @@ -44,8 +45,11 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
-
name: Update dependencies
name: Install dependencies
run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction
-
name: Install correct version of Doctrine
run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction --with-all-dependencies doctrine/orm:${{ matrix.doctrine-version }}
-
name: Run tests
run: composer check:tests
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
],
"require": {
"php": "^8.1",
"doctrine/orm": "^3.2"
"doctrine/orm": "^2.19.7 || ^3.2"
},
"require-dev": {
"doctrine/collections": "^2.2",
"doctrine/dbal": "^4",
"doctrine/dbal": "^3.9 || ^4.0",
"doctrine/persistence": "^3.3",
"editorconfig-checker/editorconfig-checker": "^10.6.0",
"ergebnis/composer-normalize": "^2.42.0",
"nette/utils": "^4",
Expand Down
11 changes: 11 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ parameters:
- ShipMonk\DoctrineEntityPreloader\Exception\RuntimeException

ignoreErrors:
-
message: '#Strict comparison using === between ReflectionProperty and null will always evaluate to false#'
identifier: 'identical.alwaysFalse'
reportUnmatched: false
path: 'src/EntityPreloader.php'
-
message: '#Result of \|\| is always false#'
identifier: 'booleanOr.alwaysFalse'
reportUnmatched: false
path: 'src/EntityPreloader.php'
-
message: '#has an uninitialized property \$id#'
identifier: 'property.uninitialized'
Expand All @@ -39,4 +49,5 @@ parameters:
path: 'tests/Fixtures/Synthetic'
-
identifier: 'property.unusedType'
reportUnmatched: false
path: 'tests/Fixtures/Synthetic'
44 changes: 20 additions & 24 deletions src/EntityPreloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

namespace ShipMonk\DoctrineEntityPreloader;

use ArrayAccess;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\QueryBuilder;
use LogicException;
Expand Down Expand Up @@ -56,19 +54,19 @@ public function preload(
$associationMapping = $sourceClassMetadata->getAssociationMapping($sourcePropertyName);

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

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

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

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

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

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

if (!$associationMapping instanceof ToManyAssociationMapping) {
throw new LogicException('Unsupported association mapping type');
}

$innerLoader = match (true) {
$associationMapping instanceof OneToManyAssociationMapping => $this->preloadOneToManyInner(...),
$associationMapping instanceof ManyToManyAssociationMapping => $this->preloadManyToManyInner(...),
$innerLoader = match ($associationMapping['type']) {
ClassMetadata::ONE_TO_MANY => $this->preloadOneToManyInner(...),
ClassMetadata::MANY_TO_MANY => $this->preloadManyToManyInner(...),
default => throw new LogicException('Unsupported association mapping type'),
};

Expand Down Expand Up @@ -238,6 +232,7 @@ private function preloadToMany(
}

/**
* @param array<string, mixed>|ArrayAccess<string, mixed> $associationMapping
* @param ClassMetadata<S> $sourceClassMetadata
* @param ClassMetadata<T> $targetClassMetadata
* @param list<mixed> $uninitializedSourceEntityIdsChunk
Expand All @@ -248,7 +243,7 @@ private function preloadToMany(
* @template T of E
*/
private function preloadOneToManyInner(
ToManyAssociationMapping $associationMapping,
array|ArrayAccess $associationMapping,
ClassMetadata $sourceClassMetadata,
ReflectionProperty $sourceIdentifierReflection,
string $sourcePropertyName,
Expand All @@ -272,7 +267,7 @@ private function preloadOneToManyInner(
$targetPropertyName,
$uninitializedSourceEntityIdsChunk,
$maxFetchJoinSameFieldCount,
$associationMapping->orderBy(),
$associationMapping['orderBy'] ?? [],
);

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

/**
* @param array<string, mixed>|ArrayAccess<string, mixed> $associationMapping
* @param ClassMetadata<S> $sourceClassMetadata
* @param ClassMetadata<T> $targetClassMetadata
* @param list<mixed> $uninitializedSourceEntityIdsChunk
Expand All @@ -298,7 +294,7 @@ private function preloadOneToManyInner(
* @template T of E
*/
private function preloadManyToManyInner(
ToManyAssociationMapping $associationMapping,
array|ArrayAccess $associationMapping,
ClassMetadata $sourceClassMetadata,
ReflectionProperty $sourceIdentifierReflection,
string $sourcePropertyName,
Expand All @@ -309,7 +305,7 @@ private function preloadManyToManyInner(
int $maxFetchJoinSameFieldCount,
): array
{
if (count($associationMapping->orderBy()) > 0) {
if (count($associationMapping['orderBy'] ?? []) > 0) {
throw new LogicException('Many-to-many associations with order by are not supported');
}

Expand Down Expand Up @@ -458,11 +454,11 @@ private function addFetchJoinsToPreventFetchDuringHydration(
}

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

$isToOne = ($associationMapping->type() & ClassMetadata::TO_ONE) !== 0;
$isToOneInversed = $isToOne && !$associationMapping->isOwningSide();
$isToOneAbstract = $isToOne && $associationMapping->isOwningSide() && count($targetClassMetadata->subClasses) > 0;
$isToOne = ($associationMapping['type'] & ClassMetadata::TO_ONE) !== 0;
$isToOneInversed = $isToOne && $associationMapping['isOwningSide'] === false;
$isToOneAbstract = $isToOne && $associationMapping['isOwningSide'] === true && count($targetClassMetadata->subClasses) > 0;

if (!$isToOneInversed && !$isToOneAbstract) {
continue;
Expand Down
38 changes: 23 additions & 15 deletions tests/EntityPreloadBlogOneHasManyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace ShipMonkTests\DoctrineEntityPreloader;

use Composer\InstalledVersions;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\QueryException;
use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Article;
use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Category;
use ShipMonkTests\DoctrineEntityPreloader\Lib\TestCase;
use function str_starts_with;

class EntityPreloadBlogOneHasManyTest extends TestCase
{
Expand Down Expand Up @@ -54,21 +56,27 @@ public function testOneHasManyWithWithManualPreloadUsingPartial(): void

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

// partial no longer works in doctrine 3.0
self::assertException(
QueryException::class,
null,
function () use ($categories): void {
$this->getEntityManager()->createQueryBuilder()
->select('PARTIAL category.{id}', 'article')
->from(Category::class, 'category')
->leftJoin('category.articles', 'article')
->where('category IN (:categories)')
->setParameter('categories', $categories)
->getQuery()
->getResult();
},
);
$query = $this->getEntityManager()->createQueryBuilder()
->select('PARTIAL category.{id}', 'article')
->from(Category::class, 'category')
->leftJoin('category.articles', 'article')
->where('category IN (:categories)')
->setParameter('categories', $categories)
->getQuery();

if (str_starts_with(InstalledVersions::getVersion('doctrine/orm') ?? 'unknown', '3.')) {
self::assertException(QueryException::class, null, static fn() => $query->getResult());

} else {
$query->getResult();

$this->readArticleTitles($categories);

self::assertAggregatedQueries([
['count' => 1, 'query' => 'SELECT * FROM category t0'],
['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN article a1_ ON c0_.id = a1_.category_id WHERE c0_.id IN (?, ?, ?, ?, ?)'],
]);
}
}

public function testOneHasManyWithFetchJoin(): void
Expand Down

0 comments on commit c9485d5

Please sign in to comment.