Skip to content

Commit

Permalink
Fix issue doctrine#11481
Browse files Browse the repository at this point in the history
  • Loading branch information
Valentin SALMERON committed Jun 24, 2024
1 parent ca3319c commit d38e933
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 60 deletions.
6 changes: 5 additions & 1 deletion src/Persisters/Collection/ManyToManyPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +34,8 @@
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
use ResolveValuesHelper;

public function delete(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
Expand Down Expand Up @@ -249,7 +253,7 @@ 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;
$params = array_merge($params, $this->getValues($value));
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
}
}
Expand Down
61 changes: 2 additions & 59 deletions src/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -99,6 +97,7 @@
class BasicEntityPersister implements EntityPersister
{
use LockSqlHelper;
use ResolveValuesHelper;

/** @var array<string,string> */
private static array $comparisonMap = [
Expand Down Expand Up @@ -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<mixed>
*/
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);
Expand Down
74 changes: 74 additions & 0 deletions src/Persisters/Traits/ResolveValuesHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Persisters\Traits;

use BackedEnum;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;

use function array_merge;
use function is_array;
use function is_object;

trait ResolveValuesHelper
{
protected EntityManagerInterface $em;

/**
* 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<mixed>
*/
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)];
}
}
52 changes: 52 additions & 0 deletions tests/Tests/Models/Enums/Book.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToMany;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\Table;

#[Entity]
#[Table(name: 'books')]
class Book
{
#[Id]
#[GeneratedValue]
#[Column]
public int $id;

#[ManyToOne(targetEntity: Library::class, inversedBy: 'books')]
#[JoinColumn(name: 'library_id', referencedColumnName: 'id')]
public Library $library;

#[Column(enumType: BookColor::class)]
public BookColor $bookColor;

#[ManyToMany(targetEntity: BookCategory::class, mappedBy: 'books')]
public Collection $categories;

public function __construct()
{
$this->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);
}
}
52 changes: 52 additions & 0 deletions tests/Tests/Models/Enums/BookCategory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToMany;

#[Entity]
class BookCategory
{
#[Id]
#[Column]
#[GeneratedValue]
public int $id;

#[Column]
public string $name;

#[ManyToMany(targetEntity: Book::class, inversedBy: 'categories')]
public Collection $books;

public function __construct()
{
$this->books = new ArrayCollection();
}

public function addBook(Book $book): void
{
$this->books->add($book);
}

public function getBooks(): Collection
{
return $this->books;
}

public function getBooksWithColor(BookColor $bookColor): Collection
{
$criteria = Criteria::create()
->andWhere(Criteria::expr()->eq('bookColor', $bookColor));

return $this->books->matching($criteria);
}
}
11 changes: 11 additions & 0 deletions tests/Tests/Models/Enums/BookColor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

enum BookColor: string
{
case RED = 'red';
case BLUE = 'blue';
}
50 changes: 50 additions & 0 deletions tests/Tests/Models/Enums/Library.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;

#[Entity]
class Library
{
#[Id]
#[GeneratedValue]
#[Column]
public int $id;

#[OneToMany(targetEntity: Book::class, mappedBy: 'library')]
public Collection $books;

public function __construct()
{
$this->books = new ArrayCollection();
}

public function getBooksWithColor(BookColor $bookColor): Collection
{
$criteria = Criteria::create()
->andWhere(Criteria::expr()->eq('bookColor', $bookColor));

return $this->books->matching($criteria);
}

public function getBooks(): Collection
{
return $this->books;
}

public function addBook(Book $book): void
{
$this->books->add($book);
$book->setLibrary($this);
}
}
Loading

0 comments on commit d38e933

Please sign in to comment.