Skip to content

Commit f38ee09

Browse files
authored
Merge pull request #12062 from janedbal/binary-id-eager-fetch-reupload
Fix unhandled ParameterType case for binary PKs
2 parents e19704e + 6ab858a commit f38ee09

File tree

5 files changed

+273
-0
lines changed

5 files changed

+273
-0
lines changed

src/Persisters/Entity/BasicEntityPersister.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,7 @@ private function getArrayBindingType(ParameterType|int|string $type): ArrayParam
19641964
ParameterType::STRING => ArrayParameterType::STRING,
19651965
ParameterType::INTEGER => ArrayParameterType::INTEGER,
19661966
ParameterType::ASCII => ArrayParameterType::ASCII,
1967+
ParameterType::BINARY => ArrayParameterType::BINARY,
19671968
};
19681969
}
19691970

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
6+
7+
use LogicException;
8+
9+
use function bin2hex;
10+
use function hex2bin;
11+
use function random_bytes;
12+
13+
class BinaryId
14+
{
15+
public const LENGTH = 6;
16+
17+
private string $hexId;
18+
19+
private function __construct(string $data)
20+
{
21+
$this->hexId = $data;
22+
}
23+
24+
public static function new(): self
25+
{
26+
return new self(bin2hex(random_bytes(self::LENGTH)));
27+
}
28+
29+
public static function fromBytes(string $value): self
30+
{
31+
return new self(bin2hex($value));
32+
}
33+
34+
public function getBytes(): string
35+
{
36+
$binary = hex2bin($this->hexId);
37+
if ($binary === false) {
38+
throw new LogicException('Cannot convert hex to binary: ' . $this->hexId);
39+
}
40+
41+
return $binary;
42+
}
43+
44+
public function __toString(): string
45+
{
46+
return $this->getBytes();
47+
}
48+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
6+
7+
use Doctrine\DBAL\ParameterType;
8+
use Doctrine\DBAL\Platforms\AbstractPlatform;
9+
use Doctrine\DBAL\Types\Type;
10+
use Doctrine\Tests\Mocks\CompatibilityType;
11+
use LogicException;
12+
13+
final class BinaryIdType extends Type
14+
{
15+
use CompatibilityType;
16+
17+
public const NAME = 'binary_id';
18+
19+
public function convertToPHPValue(
20+
mixed $value,
21+
AbstractPlatform $platform,
22+
): BinaryId|null {
23+
if ($value === null) {
24+
return null;
25+
}
26+
27+
if ($value instanceof BinaryId) {
28+
return $value;
29+
}
30+
31+
return BinaryId::fromBytes($value);
32+
}
33+
34+
public function convertToDatabaseValue(
35+
mixed $value,
36+
AbstractPlatform $platform,
37+
): string|null {
38+
if ($value === null) {
39+
return null;
40+
} elseif ($value instanceof BinaryId) {
41+
return $value->getBytes();
42+
} else {
43+
throw new LogicException('Unexpected value: ' . $value);
44+
}
45+
}
46+
47+
public function getSQLDeclaration(
48+
array $column,
49+
AbstractPlatform $platform,
50+
): string {
51+
return $platform->getBinaryTypeDeclarationSQL([
52+
'length' => BinaryId::LENGTH,
53+
'fixed' => true,
54+
]);
55+
}
56+
57+
private function doGetBindingType(): ParameterType|int
58+
{
59+
return ParameterType::BINARY;
60+
}
61+
62+
public function getName(): string
63+
{
64+
return self::NAME;
65+
}
66+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\BinaryPrimaryKey;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\Common\Collections\ReadableCollection;
10+
use Doctrine\ORM\Mapping\Column;
11+
use Doctrine\ORM\Mapping\Entity;
12+
use Doctrine\ORM\Mapping\Id;
13+
use Doctrine\ORM\Mapping\ManyToOne;
14+
use Doctrine\ORM\Mapping\OneToMany;
15+
16+
#[Entity]
17+
class Category
18+
{
19+
#[Id]
20+
#[Column(type: BinaryIdType::NAME, nullable: false)]
21+
private BinaryId $id;
22+
23+
#[Column]
24+
private string $name;
25+
26+
#[ManyToOne(targetEntity: self::class, inversedBy: 'children')]
27+
private self|null $parent;
28+
29+
/** @var Collection<int, Category> */
30+
#[OneToMany(targetEntity: self::class, mappedBy: 'parent')]
31+
private Collection $children;
32+
33+
public function __construct(
34+
string $name,
35+
self|null $parent = null,
36+
) {
37+
$this->id = BinaryId::new();
38+
$this->name = $name;
39+
$this->parent = $parent;
40+
$this->children = new ArrayCollection();
41+
42+
$parent?->addChild($this);
43+
}
44+
45+
public function getId(): BinaryId
46+
{
47+
return $this->id;
48+
}
49+
50+
public function getName(): string
51+
{
52+
return $this->name;
53+
}
54+
55+
public function getParent(): self|null
56+
{
57+
return $this->parent;
58+
}
59+
60+
/** @return ReadableCollection<int, Category> */
61+
public function getChildren(): ReadableCollection
62+
{
63+
return $this->children;
64+
}
65+
66+
/** @internal */
67+
public function addChild(self $category): void
68+
{
69+
if (! $this->children->contains($category)) {
70+
$this->children->add($category);
71+
}
72+
}
73+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Persisters;
6+
7+
use Doctrine\DBAL\DriverManager;
8+
use Doctrine\DBAL\Types\Type as DbalType;
9+
use Doctrine\ORM\EntityManager;
10+
use Doctrine\ORM\Mapping\ClassMetadata;
11+
use Doctrine\ORM\ORMSetup;
12+
use Doctrine\ORM\Tools\SchemaTool;
13+
use Doctrine\ORM\Tools\SchemaValidator;
14+
use Doctrine\Tests\Mocks\EntityManagerMock;
15+
use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType;
16+
use Doctrine\Tests\Models\BinaryPrimaryKey\Category;
17+
use Doctrine\Tests\OrmTestCase;
18+
19+
final class BinaryIdPersisterTest extends OrmTestCase
20+
{
21+
private EntityManager|null $entityManager = null;
22+
23+
public function testOneToManyWithEagerFetchMode(): void
24+
{
25+
$entityManager = $this->createEntityManager();
26+
27+
$this->createDummyBlogData($entityManager, 3);
28+
29+
$categories = $entityManager->createQueryBuilder()
30+
->select('category')
31+
->from(Category::class, 'category')
32+
->getQuery()
33+
->setFetchMode(Category::class, 'children', ClassMetadata::FETCH_EAGER)
34+
->getResult();
35+
36+
self::assertCount(3, $categories);
37+
}
38+
39+
private function createDummyBlogData(
40+
EntityManager $entityManager,
41+
int $categoryCount = 1,
42+
int $categoryParentsCount = 0,
43+
): void {
44+
for ($h = 0; $h < $categoryCount; $h++) {
45+
$categoryParent = null;
46+
47+
for ($i = 0; $i < $categoryParentsCount; $i++) {
48+
$categoryParent = new Category('CategoryParent#' . $i, $categoryParent);
49+
$entityManager->persist($categoryParent);
50+
}
51+
52+
$category = new Category('Category#' . $h, $categoryParent);
53+
$entityManager->persist($category);
54+
}
55+
56+
$entityManager->flush();
57+
$entityManager->clear();
58+
}
59+
60+
private function createEntityManager(): EntityManager
61+
{
62+
if ($this->entityManager !== null) {
63+
return $this->entityManager;
64+
}
65+
66+
$config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true);
67+
68+
if (! DbalType::hasType(BinaryIdType::NAME)) {
69+
DbalType::addType(BinaryIdType::NAME, BinaryIdType::class);
70+
}
71+
72+
$connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
73+
$entityManager = new EntityManagerMock($connection, $config);
74+
75+
$schemaTool = new SchemaTool($entityManager);
76+
$schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata());
77+
78+
$schemaValidator = new SchemaValidator($entityManager);
79+
$schemaValidator->validateMapping();
80+
81+
$this->entityManager = $entityManager;
82+
83+
return $entityManager;
84+
}
85+
}

0 commit comments

Comments
 (0)