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.2 into 2.18.x #11131

Merged
merged 6 commits into from
Dec 20, 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
5 changes: 5 additions & 0 deletions docs/en/reference/dql-doctrine-query-language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,11 @@ hierarchies:
$query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee');
$query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1');
$query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1');
$query->setParameter(0, $em->getClassMetadata(CompanyEmployee::class));

.. note::
To use a class as parameter, you have to bind its class metadata:
``$query->setParameter(0, $em->getClassMetadata(CompanyEmployee::class);``.

Get all users visible on a given website that have chosen certain gender:

Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ORM/EntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ public static function create($connection, Configuration $config, ?EventManager
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9961',
'%s() is deprecated. To boostrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.',
'%s() is deprecated. To bootstrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.',
__METHOD__,
DriverManager::class,
self::class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ protected function configure()
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
->addOption('skip-mapping', null, InputOption::VALUE_NONE, 'Skip the mapping validation check')
->addOption('skip-sync', null, InputOption::VALUE_NONE, 'Skip checking if the mapping is in sync with the database')
->addOption('skip-property-types', null, InputOption::VALUE_NONE, 'Skip checking if property types match the Doctrine types')
->setHelp('Validate that the mapping files are correct and in sync with the database.');
}

Expand All @@ -39,7 +40,7 @@ private function doExecute(InputInterface $input, OutputInterface $output): int
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();

$em = $this->getEntityManager($input);
$validator = new SchemaValidator($em);
$validator = new SchemaValidator($em, ! $input->getOption('skip-property-types'));
$exit = 0;

$ui->section('Mapping');
Expand Down
51 changes: 44 additions & 7 deletions lib/Doctrine/ORM/Tools/SchemaValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use function get_class;
use function implode;
use function in_array;
use function interface_exists;
use function is_a;
use function sprintf;

Expand All @@ -56,6 +57,9 @@ class SchemaValidator
/** @var EntityManagerInterface */
private $em;

/** @var bool */
private $validatePropertyTypes;

/**
* It maps built-in Doctrine types to PHP types
*/
Expand All @@ -74,9 +78,10 @@ class SchemaValidator
TextType::class => 'string',
];

public function __construct(EntityManagerInterface $em)
public function __construct(EntityManagerInterface $em, bool $validatePropertyTypes = true)
{
$this->em = $em;
$this->em = $em;
$this->validatePropertyTypes = $validatePropertyTypes;
}

/**
Expand Down Expand Up @@ -136,7 +141,7 @@ public function validateClass(ClassMetadataInfo $class)
}

// PHP 7.4 introduces the ability to type properties, so we can't validate them in previous versions
if (PHP_VERSION_ID >= 70400) {
if (PHP_VERSION_ID >= 70400 && $this->validatePropertyTypes) {
array_push($ce, ...$this->validatePropertiesTypes($class));
}

Expand Down Expand Up @@ -389,10 +394,20 @@ function (array $fieldMapping) use ($class): ?string {
return null;
}

if (
is_a($propertyType, BackedEnum::class, true)
&& $metadataFieldType === (string) (new ReflectionEnum($propertyType))->getBackingType()
) {
if (is_a($propertyType, BackedEnum::class, true)) {
$backingType = (string) (new ReflectionEnum($propertyType))->getBackingType();

if ($metadataFieldType !== $backingType) {
return sprintf(
"The field '%s#%s' has the property type '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
$class->name,
$fieldName,
$propertyType,
$backingType,
$metadataFieldType
);
}

if (! isset($fieldMapping['enumType']) || $propertyType === $fieldMapping['enumType']) {
return null;
}
Expand All @@ -406,6 +421,28 @@ function (array $fieldMapping) use ($class): ?string {
);
}

if (
isset($fieldMapping['enumType'])
&& $propertyType !== $fieldMapping['enumType']
&& interface_exists($propertyType)
&& is_a($fieldMapping['enumType'], $propertyType, true)
) {
$backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType();

if ($metadataFieldType === $backingType) {
return null;
}

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

if (
$fieldMapping['type'] === 'json'
&& in_array($propertyType, ['string', 'int', 'float', 'bool', 'true', 'false', 'null'], true)
Expand Down
27 changes: 18 additions & 9 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH10661/GH10661Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,36 @@ final class GH10661Test extends OrmTestCase
/** @var EntityManagerInterface */
private $em;

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

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

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

self::assertEquals(
self::assertSame(
["The field 'Doctrine\Tests\ORM\Functional\Ticket\GH10661\InvalidEntity#property1' has the property type 'float' that differs from the metadata field type 'string' returned by the 'decimal' DBAL type."],
$ce
);
}

public function testPropertyTypeErrorsCanBeSilenced(): void
{
$class = $this->em->getClassMetadata(InvalidEntity::class);
$ce = $this->bootstrapValidator(false)->validateClass($class);

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

public function testMetadataFieldTypeNotCoherentWithEntityPropertyTypeWithInheritance(): void
{
$class = $this->em->getClassMetadata(InvalidChildEntity::class);
$ce = $this->validator->validateClass($class);
$ce = $this->bootstrapValidator()->validateClass($class);

self::assertEquals(
self::assertSame(
[
"The field 'Doctrine\Tests\ORM\Functional\Ticket\GH10661\InvalidChildEntity#property1' has the property type 'float' that differs from the metadata field type 'string' returned by the 'decimal' DBAL type.",
"The field 'Doctrine\Tests\ORM\Functional\Ticket\GH10661\InvalidChildEntity#property2' has the property type 'int' that differs from the metadata field type 'string' returned by the 'string' DBAL type.",
Expand All @@ -50,4 +54,9 @@ public function testMetadataFieldTypeNotCoherentWithEntityPropertyTypeWithInheri
$ce
);
}

private function bootstrapValidator(bool $validatePropertyTypes = true): SchemaValidator
{
return new SchemaValidator($this->em, $validatePropertyTypes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

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

interface EntityStatus
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public function testMetadataFieldTypeNotCoherentWithEntityPropertyType(): void

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#status1' has the property type 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\StringEntityStatus' with a backing type of 'string' that differs from the metadata field type 'int'.",
"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'.",
"The field 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\InvalidEntityWithTypedEnum#status3' has the metadata enumType 'Doctrine\Tests\ORM\Functional\Ticket\GH11037\StringEntityStatus' with a backing type of 'string' that differs from the metadata field type 'int'.",
],
$ce
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ class InvalidEntityWithTypedEnum
* @Column(type="integer", enumType=StringEntityStatus::class)
*/
protected IntEntityStatus $status2;

/**
* @Column(type="integer", enumType=StringEntityStatus::class)
*/
protected EntityStatus $status3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

enum StringEntityStatus: string
enum StringEntityStatus: string implements EntityStatus
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ class ValidEntityWithTypedEnum
* @Column(type="smallint", enumType=IntEntityStatus::class)
*/
protected IntEntityStatus $status2;

/**
* @Column(type="string", enumType=StringEntityStatus::class)
*/
protected EntityStatus $status3;
}