diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 7a84afd4f83..bf9b9f9ed5f 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -667,16 +667,6 @@
true]]]>
true]]]>
-
-
-
-
-
-
-
-
- ]]>
-
diff --git a/src/Persisters/Collection/ManyToManyPersister.php b/src/Persisters/Collection/ManyToManyPersister.php
index 7cf993d5997..5a037ad46f9 100644
--- a/src/Persisters/Collection/ManyToManyPersister.php
+++ b/src/Persisters/Collection/ManyToManyPersister.php
@@ -15,10 +15,12 @@
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\SqlValueVisitor;
+use Doctrine\ORM\Persisters\Traits\ResolveValuesHelper;
use Doctrine\ORM\Query;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
+use function array_merge;
use function array_pop;
use function assert;
use function count;
@@ -32,6 +34,8 @@
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
+ use ResolveValuesHelper;
+
public function delete(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
@@ -238,7 +242,8 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri
$paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em);
}
- $parameters = $this->expandCriteriaParameters($criteria);
+ $parameters = $this->expandCriteriaParameters($criteria);
+ $paramsValues = [];
foreach ($parameters as $parameter) {
[$name, $value, $operator] = $parameter;
@@ -249,11 +254,13 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
- $params[] = $value;
+ $paramsValues[] = $this->getValues($value);
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
}
}
+ $params = array_merge($params, ...$paramsValues);
+
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform);
diff --git a/src/Persisters/Entity/BasicEntityPersister.php b/src/Persisters/Entity/BasicEntityPersister.php
index 377e03ce274..399abccee81 100644
--- a/src/Persisters/Entity/BasicEntityPersister.php
+++ b/src/Persisters/Entity/BasicEntityPersister.php
@@ -4,7 +4,6 @@
namespace Doctrine\ORM\Persisters\Entity;
-use BackedEnum;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Order;
@@ -31,7 +30,7 @@
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
use Doctrine\ORM\Persisters\SqlExpressionVisitor;
use Doctrine\ORM\Persisters\SqlValueVisitor;
-use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
+use Doctrine\ORM\Persisters\Traits\ResolveValuesHelper;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
@@ -53,7 +52,6 @@
use function count;
use function implode;
use function is_array;
-use function is_object;
use function reset;
use function spl_object_id;
use function sprintf;
@@ -99,6 +97,7 @@
class BasicEntityPersister implements EntityPersister
{
use LockSqlHelper;
+ use ResolveValuesHelper;
/** @var array */
private static array $comparisonMap = [
@@ -1912,62 +1911,6 @@ private function getArrayBindingType(ParameterType|int|string $type): ArrayParam
};
}
- /**
- * Retrieves the parameters that identifies a value.
- *
- * @return mixed[]
- */
- private function getValues(mixed $value): array
- {
- if (is_array($value)) {
- $newValue = [];
-
- foreach ($value as $itemValue) {
- $newValue = array_merge($newValue, $this->getValues($itemValue));
- }
-
- return [$newValue];
- }
-
- return $this->getIndividualValue($value);
- }
-
- /**
- * Retrieves an individual parameter value.
- *
- * @psalm-return list
- */
- private function getIndividualValue(mixed $value): array
- {
- if (! is_object($value)) {
- return [$value];
- }
-
- if ($value instanceof BackedEnum) {
- return [$value->value];
- }
-
- $valueClass = DefaultProxyClassNameResolver::getClass($value);
-
- if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
- return [$value];
- }
-
- $class = $this->em->getClassMetadata($valueClass);
-
- if ($class->isIdentifierComposite) {
- $newValue = [];
-
- foreach ($class->getIdentifierValues($value) as $innerValue) {
- $newValue = array_merge($newValue, $this->getValues($innerValue));
- }
-
- return $newValue;
- }
-
- return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
- }
-
public function exists(object $entity, Criteria|null $extraConditions = null): bool
{
$criteria = $this->class->getIdentifierValues($entity);
diff --git a/src/Persisters/Traits/ResolveValuesHelper.php b/src/Persisters/Traits/ResolveValuesHelper.php
new file mode 100644
index 00000000000..abfaaf1d5b3
--- /dev/null
+++ b/src/Persisters/Traits/ResolveValuesHelper.php
@@ -0,0 +1,72 @@
+
+ */
+ private function getValues(mixed $value): array
+ {
+ if (is_array($value)) {
+ $newValues = [];
+
+ foreach ($value as $itemValue) {
+ $newValues[] = $this->getValues($itemValue);
+ }
+
+ return [array_merge(...$newValues)];
+ }
+
+ return $this->getIndividualValue($value);
+ }
+
+ /**
+ * Retrieves an individual parameter value.
+ *
+ * @psalm-return list
+ */
+ private function getIndividualValue(mixed $value): array
+ {
+ if (! is_object($value)) {
+ return [$value];
+ }
+
+ if ($value instanceof BackedEnum) {
+ return [$value->value];
+ }
+
+ $valueClass = DefaultProxyClassNameResolver::getClass($value);
+
+ if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
+ return [$value];
+ }
+
+ $class = $this->em->getClassMetadata($valueClass);
+
+ if ($class->isIdentifierComposite) {
+ $newValues = [];
+
+ foreach ($class->getIdentifierValues($value) as $innerValue) {
+ $newValues[] = $this->getValues($innerValue);
+ }
+
+ return array_merge(...$newValues);
+ }
+
+ return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
+ }
+}
diff --git a/tests/Tests/Models/Enums/Book.php b/tests/Tests/Models/Enums/Book.php
new file mode 100644
index 00000000000..ec7f960927a
--- /dev/null
+++ b/tests/Tests/Models/Enums/Book.php
@@ -0,0 +1,52 @@
+categories = new ArrayCollection();
+ }
+
+ public function setLibrary(Library $library): void
+ {
+ $this->library = $library;
+ }
+
+ public function addCategory(BookCategory $bookCategory): void
+ {
+ $this->categories->add($bookCategory);
+ $bookCategory->addBook($this);
+ }
+}
diff --git a/tests/Tests/Models/Enums/BookCategory.php b/tests/Tests/Models/Enums/BookCategory.php
new file mode 100644
index 00000000000..e7d3a0eb176
--- /dev/null
+++ b/tests/Tests/Models/Enums/BookCategory.php
@@ -0,0 +1,43 @@
+books = new ArrayCollection();
+ }
+
+ public function addBook(Book $book): void
+ {
+ $this->books->add($book);
+ }
+
+ public function getBooks(): Collection
+ {
+ return $this->books;
+ }
+}
diff --git a/tests/Tests/Models/Enums/BookColor.php b/tests/Tests/Models/Enums/BookColor.php
new file mode 100644
index 00000000000..66c3e8664e0
--- /dev/null
+++ b/tests/Tests/Models/Enums/BookColor.php
@@ -0,0 +1,11 @@
+books = new ArrayCollection();
+ }
+
+ public function getBooks(): Collection
+ {
+ return $this->books;
+ }
+
+ public function addBook(Book $book): void
+ {
+ $this->books->add($book);
+ $book->setLibrary($this);
+ }
+}
diff --git a/tests/Tests/ORM/Functional/EnumTest.php b/tests/Tests/ORM/Functional/EnumTest.php
index 75ccac8b506..e6d3f94c59c 100644
--- a/tests/Tests/ORM/Functional/EnumTest.php
+++ b/tests/Tests/ORM/Functional/EnumTest.php
@@ -4,6 +4,8 @@
namespace Doctrine\Tests\ORM\Functional;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
@@ -12,9 +14,13 @@
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithArrayOfEnums;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithEnum;
+use Doctrine\Tests\Models\Enums\Book;
+use Doctrine\Tests\Models\Enums\BookCategory;
+use Doctrine\Tests\Models\Enums\BookColor;
use Doctrine\Tests\Models\Enums\Card;
use Doctrine\Tests\Models\Enums\CardWithDefault;
use Doctrine\Tests\Models\Enums\CardWithNullable;
+use Doctrine\Tests\Models\Enums\Library;
use Doctrine\Tests\Models\Enums\Product;
use Doctrine\Tests\Models\Enums\Quantity;
use Doctrine\Tests\Models\Enums\Scale;
@@ -74,7 +80,6 @@ public function testEnumHydrationObjectHydrator(): void
$this->_em->persist($card2);
$this->_em->flush();
- unset($card1, $card2);
$this->_em->clear();
/** @var list $foundCards */
@@ -402,7 +407,6 @@ public function testFindByEnum(): void
$this->_em->persist($card2);
$this->_em->flush();
- unset($card1, $card2);
$this->_em->clear();
/** @var list $foundCards */
@@ -516,4 +520,152 @@ public function testEnumWithDefault(): void
self::assertSame(Suit::Hearts, $card->suit);
}
+
+ public function testEnumLazyCollectionMatchingWithOneToMany(): void
+ {
+ $this->setUpEntitySchema([Book::class, Library::class]);
+
+ $redBook = new Book();
+ $redBook->bookColor = BookColor::RED;
+
+ $blueBook = new Book();
+ $blueBook->bookColor = BookColor::BLUE;
+
+ $library = new Library();
+ $library->addBook($blueBook);
+ $library->addBook($redBook);
+
+ $this->_em->persist($library);
+ $this->_em->persist($blueBook);
+ $this->_em->persist($redBook);
+
+ $this->_em->flush();
+ $libraryId = $library->id;
+
+ $this->_em->clear();
+
+ $library = $this->_em->find(Library::class, $libraryId);
+
+ $redBooks = $library->books->matching(
+ Criteria::create()->andWhere(Criteria::expr()->eq('bookColor', BookColor::RED)),
+ );
+
+ $this->assertCount(1, $redBooks);
+ }
+
+ public function testEnumInitializedCollectionMatchingWithOneToMany(): void
+ {
+ $this->setUpEntitySchema([Book::class, Library::class]);
+
+ $redBook = new Book();
+ $redBook->bookColor = BookColor::RED;
+
+ $blueBook = new Book();
+ $blueBook->bookColor = BookColor::BLUE;
+
+ $library = new Library();
+ $library->addBook($blueBook);
+ $library->addBook($redBook);
+
+ $this->_em->persist($library);
+ $this->_em->persist($blueBook);
+ $this->_em->persist($redBook);
+
+ $this->_em->flush();
+ $libraryId = $library->id;
+
+ $this->_em->clear();
+
+ $library = $this->_em->find(Library::class, $libraryId);
+ $this->assertInstanceOf(Library::class, $library);
+
+ // Load books collection first
+ $this->assertCount(2, $library->getBooks());
+
+ $redBooks = $library->books->matching(
+ Criteria::create()->andWhere(Criteria::expr()->eq('bookColor', BookColor::RED)),
+ );
+
+ $this->assertCount(1, $redBooks);
+ }
+
+ public function testEnumLazyCollectionMatchingWithManyToMany(): void
+ {
+ $this->setUpEntitySchema([Book::class, BookCategory::class, Library::class]);
+
+ $thrillerCategory = new BookCategory();
+ $thrillerCategory->name = 'thriller';
+
+ $fantasyCategory = new BookCategory();
+ $fantasyCategory->name = 'fantasy';
+
+ $redBook = new Book();
+ $redBook->addCategory($fantasyCategory);
+ $redBook->addCategory($thrillerCategory);
+ $redBook->bookColor = BookColor::RED;
+
+ $blueBook = new Book();
+ $blueBook->addCategory($thrillerCategory);
+ $blueBook->bookColor = BookColor::BLUE;
+
+ $this->_em->persist($thrillerCategory);
+ $this->_em->persist($fantasyCategory);
+ $this->_em->persist($blueBook);
+ $this->_em->persist($redBook);
+
+ $this->_em->flush();
+ $thrillerCategoryId = $thrillerCategory->id;
+
+ $this->_em->clear();
+
+ $thrillerCategory = $this->_em->find(BookCategory::class, $thrillerCategoryId);
+
+ $redBooks = $thrillerCategory->books->matching(
+ Criteria::create()->andWhere(Criteria::expr()->eq('bookColor', BookColor::RED)),
+ );
+
+ $this->assertCount(1, $redBooks);
+ }
+
+ public function testEnumInitializedCollectionMatchingWithManyToMany(): void
+ {
+ $this->setUpEntitySchema([Book::class, BookCategory::class, Library::class]);
+
+ $thrillerCategory = new BookCategory();
+ $thrillerCategory->name = 'thriller';
+
+ $fantasyCategory = new BookCategory();
+ $fantasyCategory->name = 'fantasy';
+
+ $redBook = new Book();
+ $redBook->addCategory($fantasyCategory);
+ $redBook->addCategory($thrillerCategory);
+ $redBook->bookColor = BookColor::RED;
+
+ $blueBook = new Book();
+ $blueBook->addCategory($thrillerCategory);
+ $blueBook->bookColor = BookColor::BLUE;
+
+ $this->_em->persist($thrillerCategory);
+ $this->_em->persist($fantasyCategory);
+ $this->_em->persist($blueBook);
+ $this->_em->persist($redBook);
+
+ $this->_em->flush();
+ $thrillerCategoryId = $thrillerCategory->id;
+
+ $this->_em->clear();
+
+ $thrillerCategory = $this->_em->find(BookCategory::class, $thrillerCategoryId);
+ $this->assertInstanceOf(BookCategory::class, $thrillerCategory);
+
+ // Load books collection first
+ $this->assertCount(2, $thrillerCategory->getBooks());
+
+ $redBooks = $thrillerCategory->books->matching(
+ Criteria::create()->andWhere(Criteria::expr()->eq('bookColor', BookColor::RED)),
+ );
+
+ $this->assertCount(1, $redBooks);
+ }
}