Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,7 @@ private function getArrayBindingType(ParameterType|int|string $type): ArrayParam
ParameterType::STRING => ArrayParameterType::STRING,
ParameterType::INTEGER => ArrayParameterType::INTEGER,
ParameterType::ASCII => ArrayParameterType::ASCII,
ParameterType::BINARY => ArrayParameterType::BINARY,
};
}

Expand Down
48 changes: 48 additions & 0 deletions tests/Tests/Models/BinaryPrimaryKey/BinaryId.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\BinaryPrimaryKey;

use LogicException;

use function bin2hex;
use function hex2bin;
use function random_bytes;

class BinaryId
{
public const LENGTH = 6;

private string $hexId;

private function __construct(string $data)
{
$this->hexId = $data;
}

public static function new(): self
{
return new self(bin2hex(random_bytes(self::LENGTH)));
}

public static function fromBytes(string $value): self
{
return new self(bin2hex($value));
}

public function getBytes(): string
{
$binary = hex2bin($this->hexId);
if ($binary === false) {
throw new LogicException('Cannot convert hex to binary: ' . $this->hexId);
}

return $binary;
}

public function __toString(): string
{
return $this->getBytes();
}
}
66 changes: 66 additions & 0 deletions tests/Tests/Models/BinaryPrimaryKey/BinaryIdType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\BinaryPrimaryKey;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\Tests\Mocks\CompatibilityType;
use LogicException;

final class BinaryIdType extends Type
{
use CompatibilityType;

public const NAME = 'binary_id';

public function convertToPHPValue(
mixed $value,
AbstractPlatform $platform,
): BinaryId|null {
if ($value === null) {
return null;
}

if ($value instanceof BinaryId) {
return $value;
}

return BinaryId::fromBytes($value);
}

public function convertToDatabaseValue(
mixed $value,
AbstractPlatform $platform,
): string|null {
if ($value === null) {
return null;
} elseif ($value instanceof BinaryId) {
return $value->getBytes();
} else {
throw new LogicException('Unexpected value: ' . $value);
}
}

public function getSQLDeclaration(
array $column,
AbstractPlatform $platform,
): string {
return $platform->getBinaryTypeDeclarationSQL([
'length' => BinaryId::LENGTH,
'fixed' => true,
]);
}

private function doGetBindingType(): ParameterType|int
{
return ParameterType::BINARY;
}

public function getName(): string
{
return self::NAME;
}
}
73 changes: 73 additions & 0 deletions tests/Tests/Models/BinaryPrimaryKey/Category.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\BinaryPrimaryKey;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;

#[Entity]
class Category
{
#[Id]
#[Column(type: BinaryIdType::NAME, nullable: false)]
private BinaryId $id;

#[Column]
private string $name;

#[ManyToOne(targetEntity: self::class, inversedBy: 'children')]
private self|null $parent;

/** @var Collection<int, Category> */
#[OneToMany(targetEntity: self::class, mappedBy: 'parent')]
private Collection $children;

public function __construct(
string $name,
self|null $parent = null,
) {
$this->id = BinaryId::new();
$this->name = $name;
$this->parent = $parent;
$this->children = new ArrayCollection();

$parent?->addChild($this);
}

public function getId(): BinaryId
{
return $this->id;
}

public function getName(): string
{
return $this->name;
}

public function getParent(): self|null
{
return $this->parent;
}

/** @return ReadableCollection<int, Category> */
public function getChildren(): ReadableCollection
{
return $this->children;
}

/** @internal */
public function addChild(self $category): void
{
if (! $this->children->contains($category)) {
$this->children->add($category);
}
}
}
85 changes: 85 additions & 0 deletions tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Persisters;

use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type as DbalType;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType;
use Doctrine\Tests\Models\BinaryPrimaryKey\Category;
use Doctrine\Tests\OrmTestCase;

final class BinaryIdPersisterTest extends OrmTestCase
{
private EntityManager|null $entityManager = null;

public function testOneToManyWithEagerFetchMode(): void
{
$entityManager = $this->createEntityManager();

$this->createDummyBlogData($entityManager, 3);

$categories = $entityManager->createQueryBuilder()
->select('category')
->from(Category::class, 'category')
->getQuery()
->setFetchMode(Category::class, 'children', ClassMetadata::FETCH_EAGER)
->getResult();

self::assertCount(3, $categories);
}

private function createDummyBlogData(
EntityManager $entityManager,
int $categoryCount = 1,
int $categoryParentsCount = 0,
): void {
for ($h = 0; $h < $categoryCount; $h++) {
$categoryParent = null;

for ($i = 0; $i < $categoryParentsCount; $i++) {
$categoryParent = new Category('CategoryParent#' . $i, $categoryParent);
$entityManager->persist($categoryParent);
}

$category = new Category('Category#' . $h, $categoryParent);
$entityManager->persist($category);
}

$entityManager->flush();
$entityManager->clear();
}

private function createEntityManager(): EntityManager
{
if ($this->entityManager !== null) {
return $this->entityManager;
}

$config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true);

if (! DbalType::hasType(BinaryIdType::NAME)) {
DbalType::addType(BinaryIdType::NAME, BinaryIdType::class);
}

$connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
$entityManager = new EntityManagerMock($connection, $config);

$schemaTool = new SchemaTool($entityManager);
$schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata());

$schemaValidator = new SchemaValidator($entityManager);
$schemaValidator->validateMapping();

$this->entityManager = $entityManager;

return $entityManager;
}
}