Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release 2.17.1 into 3.0.x #11066

Merged
merged 5 commits into from
Nov 17, 2023
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
20 changes: 20 additions & 0 deletions lib/Doctrine/ORM/Tools/SchemaValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\ORM\Tools;

use BackedEnum;
use Doctrine\DBAL\Types\AsciiStringType;
use Doctrine\DBAL\Types\BigIntType;
use Doctrine\DBAL\Types\BooleanType;
Expand All @@ -20,6 +21,7 @@
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\FieldMapping;
use ReflectionEnum;
use ReflectionNamedType;

use function array_diff;
Expand All @@ -36,6 +38,7 @@
use function get_class;
use function implode;
use function in_array;
use function is_a;
use function sprintf;

use const PHP_VERSION_ID;
Expand Down Expand Up @@ -355,6 +358,23 @@ function (FieldMapping $fieldMapping) use ($class): string|null {
return null;
}

if (
is_a($propertyType, BackedEnum::class, true)
&& $metadataFieldType === (string) (new ReflectionEnum($propertyType))->getBackingType()
) {
if (! isset($fieldMapping['enumType']) || $propertyType === $fieldMapping['enumType']) {
return null;
}

return sprintf(
"The field '%s#%s' has the property type '%s' that differs from the metadata enumType '%s'.",
$class->name,
$fieldName,
$propertyType,
$fieldMapping['enumType'],
);
}

return sprintf(
"The field '%s#%s' has the property type '%s' that differs from the metadata field type '%s' returned by the '%s' DBAL type.",
$class->name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\Tests\ORM\Functional;

use Closure;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Exec\SingleSelectExecutor;
use Doctrine\ORM\Query\ParserResult;
Expand All @@ -12,6 +13,8 @@
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use ReflectionMethod;
use Symfony\Component\VarExporter\Instantiator;
use Symfony\Component\VarExporter\VarExporter;

use function file_get_contents;
use function rtrim;
Expand All @@ -27,21 +30,43 @@ protected function setUp(): void
parent::setUp();
}

public function testSerializeParserResult(): void
/** @param Closure(ParserResult): ParserResult $toSerializedAndBack */
#[DataProvider('provideToSerializedAndBack')]
public function testSerializeParserResult(Closure $toSerializedAndBack): void
{
$query = $this->_em
->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyEmployee u WHERE u.name = :name');

$parserResult = self::parseQuery($query);
$serialized = serialize($parserResult);
$unserialized = unserialize($serialized);
$unserialized = $toSerializedAndBack($parserResult);

$this->assertInstanceOf(ParserResult::class, $unserialized);
$this->assertInstanceOf(ResultSetMapping::class, $unserialized->getResultSetMapping());
$this->assertEquals(['name' => [0]], $unserialized->getParameterMappings());
$this->assertInstanceOf(SingleSelectExecutor::class, $unserialized->getSqlExecutor());
}

/** @return Generator<string, array{Closure(ParserResult): ParserResult}> */
public function provideToSerializedAndBack(): Generator
{
yield 'native serialization function' => [
static function (ParserResult $parserResult): ParserResult {
return unserialize(serialize($parserResult));
},
];

$instantiatorMethod = new ReflectionMethod(Instantiator::class, 'instantiate');
if ($instantiatorMethod->getReturnType() === null) {
$this->markTestSkipped('symfony/var-exporter 5.4+ is required.');
}

yield 'symfony/var-exporter' => [
static function (ParserResult $parserResult): ParserResult {
return eval('return ' . VarExporter::export($parserResult) . ';');
},
];
}

#[DataProvider('provideSerializedSingleSelectResults')]
public function testUnserializeSingleSelectResult(string $serialized): void
{
Expand Down
46 changes: 46 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH11037/GH11037Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Tests\OrmTestCase;

final class GH11037Test extends OrmTestCase
{
/** @var EntityManagerInterface */
private $em;

/** @var SchemaValidator */
private $validator;

protected function setUp(): void
{
$this->em = $this->getTestEntityManager();
$this->validator = new SchemaValidator($this->em);
}

public function testMetadataFieldTypeCoherentWithEntityPropertyType(): void
{
$class = $this->em->getClassMetadata(ValidEntityWithTypedEnum::class);
$ce = $this->validator->validateClass($class);

self::assertEquals([], $ce);
}

public function testMetadataFieldTypeNotCoherentWithEntityPropertyType(): void
{
$class = $this->em->getClassMetadata(InvalidEntityWithTypedEnum::class);
$ce = $this->validator->validateClass($class);

self::assertEquals(
[
"The field 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\InvalidEntityWithTypedEnum#status1' has the property type 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\StringEntityStatus' that differs from the metadata field type 'int' returned by the 'integer' DBAL type.",
"The field 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\InvalidEntityWithTypedEnum#status2' has the property type 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\IntEntityStatus' that differs from the metadata enumType 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\StringEntityStatus'.",
],
$ce,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;

enum IntEntityStatus: int
{
case ACTIVE = 0;
case INACTIVE = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;

#[Entity]
class InvalidEntityWithTypedEnum
{
#[Id]
#[Column]
protected int $id;

#[Column(type: 'integer', enumType: StringEntityStatus::class)]
protected StringEntityStatus $status1;

#[Column(type: 'integer', enumType: StringEntityStatus::class)]
protected IntEntityStatus $status2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;

enum StringEntityStatus: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;

#[Entity]
class ValidEntityWithTypedEnum
{
#[Id]
#[Column]
protected int $id;

#[Column(type: 'string', enumType: StringEntityStatus::class)]
protected StringEntityStatus $status1;

#[Column(type: 'smallint', enumType: IntEntityStatus::class)]
protected IntEntityStatus $status2;
}