Compare commits

...

14 Commits

Author SHA1 Message Date
Alexander M. Turek
393679a479 Allow to skip property type validation (#11130) 2023-12-20 22:47:52 +01:00
Grégoire Paris
e50ae06fe7 Merge pull request #11122 from yceruto/enum_with_interface
Fix enum mapping validation
2023-12-15 08:12:07 +01:00
Yonel Ceruto
05ef1f4f96 Fixed enum mapping validation 2023-12-14 16:53:12 -05:00
Grégoire Paris
2b91edc525 Merge pull request #11115 from localheinz/fix/typo
Fix: Typo
2023-12-12 16:42:52 +01:00
Andreas Möller
6af7f9f7bf Fix: Typo 2023-12-12 16:33:54 +01:00
flaushi
46cb9a980b Added a note parameter type for the INSTANCE OF DQL expression (#7963)
Co-authored-by: flaushi <flaushi@users.noreply.github.com>
2023-12-12 14:51:31 +01:00
flack
ed1df148c2 Fix method name in code example (#11104) 2023-12-04 21:07:27 +01:00
Tomas Norkūnas
44e943e100 Fix JSON mapping linting against subset of builtin types (#11076) 2023-12-02 11:32:08 +01:00
Cliff Odijk
23d36c0d52 Add compatibility with the Symfony 4.4 VarExporter (#10948) 2023-12-01 19:23:51 +01:00
Alexander M. Turek
212edaa80b PHPStan 5.16.0, Symfony 7.0 (#11095) 2023-11-29 14:35:05 +01:00
Grégoire Paris
1a4fe6e0bb Merge pull request #11065 from kerbert101/fix-sleep-buhh
AbstractSqlExecutor::__sleep should return property names
2023-11-17 07:25:40 +01:00
Grégoire Paris
0a7b939623 Merge pull request #11039 from yceruto/enum_validation
Adds metadata field type and enumType validation against Entity property type
2023-11-16 23:45:38 +01:00
Albert Bakker
6be65ebc70 fix: return property names in AbstractSqlExecutor::__sleep
Property names as returned by a cast to array are mangled, and that
mangling is not documented. Returning unprefixed produces the same
result, and is more likely to be supported by external tools relying on
the documented possible return values of __sleep.

For instance symfony/var-exporter does not support mangled names, which
leads to issues when caching query parsing results in Symfony
applications.
2023-11-16 19:18:25 +01:00
Yonel Ceruto
7613f25d57 Adds metadata field type and enumType validation against Entity property type 2023-11-06 08:23:58 -05:00
20 changed files with 427 additions and 77 deletions

View File

@@ -46,10 +46,10 @@
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.30.0 || 5.15.0"
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
"vimeo/psalm": "4.30.0 || 5.16.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"

View File

@@ -127,7 +127,7 @@ the targetEntity resolution will occur reliably:
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$connection = \Doctrine\DBAL\DriverManager::createConnection($connectionOptions, $config, $evm);
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
Final Thoughts

View File

@@ -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:

View File

@@ -987,7 +987,7 @@ class EntityManager implements EntityManagerInterface
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

View File

@@ -11,7 +11,9 @@ use Doctrine\DBAL\Types\Type;
use function array_diff;
use function array_keys;
use function array_map;
use function array_values;
use function str_replace;
/**
* Base class for SQL statement executors.
@@ -84,7 +86,9 @@ abstract class AbstractSqlExecutor
serialized representation becomes compatible with 3.0.x, meaning
there will not be a deprecation warning about a missing property
when unserializing data */
return array_values(array_diff(array_keys((array) $this), ["\0*\0_sqlStatements"]));
return array_values(array_diff(array_map(static function (string $prop): string {
return str_replace("\0*\0", '', $prop);
}, array_keys((array) $this)), ['_sqlStatements']));
}
public function __wakeup(): void

View File

@@ -141,7 +141,9 @@ class ParserResult
{
foreach (self::LEGACY_PROPERTY_MAPPING as $property => $legacyProperty) {
$this->$property = $data[sprintf("\0%s\0%s", self::class, $legacyProperty)]
?? $data[self::class][$legacyProperty]
?? $data[sprintf("\0%s\0%s", self::class, $property)]
?? $data[self::class][$property]
?? $this->$property
?? null;
}

View File

@@ -31,6 +31,7 @@ class ValidateSchemaCommand extends AbstractEntityManagerCommand
->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.');
}
@@ -39,7 +40,7 @@ class ValidateSchemaCommand extends AbstractEntityManagerCommand
$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');

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools;
use BackedEnum;
use Doctrine\DBAL\Types\AsciiStringType;
use Doctrine\DBAL\Types\BigIntType;
use Doctrine\DBAL\Types\BooleanType;
@@ -21,6 +22,7 @@ use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use ReflectionEnum;
use ReflectionNamedType;
use function array_diff;
@@ -37,6 +39,8 @@ use function count;
use function get_class;
use function implode;
use function in_array;
use function interface_exists;
use function is_a;
use function sprintf;
use const PHP_VERSION_ID;
@@ -53,6 +57,9 @@ class SchemaValidator
/** @var EntityManagerInterface */
private $em;
/** @var bool */
private $validatePropertyTypes;
/**
* It maps built-in Doctrine types to PHP types
*/
@@ -71,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;
}
/**
@@ -133,7 +141,7 @@ class SchemaValidator
}
// 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));
}
@@ -368,7 +376,7 @@ class SchemaValidator
}
// If the property type is not a named type, we cannot check it
if (! ($propertyType instanceof ReflectionNamedType)) {
if (! ($propertyType instanceof ReflectionNamedType) || $propertyType->getName() === 'mixed') {
return null;
}
@@ -386,6 +394,62 @@ class SchemaValidator
return null;
}
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;
}
return sprintf(
"The field '%s#%s' has the property type '%s' that differs from the metadata enumType '%s'.",
$class->name,
$fieldName,
$propertyType,
$fieldMapping['enumType']
);
}
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)
) {
return null;
}
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,

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.15.0@5c774aca4746caf3d239d9c8cadb9f882ca29352">
<files psalm-version="5.16.0@2897ba636551a8cb61601cc26f6ccfbba6c36591">
<file src="lib/Doctrine/ORM/AbstractQuery.php">
<DeprecatedClass>
<code>IterableResult</code>
@@ -590,7 +590,6 @@
<DocblockTypeContradiction>
<code><![CDATA[! $mapping['isOwningSide']]]></code>
<code><![CDATA[! $this->table]]></code>
<code><![CDATA[! class_exists($mapping['targetEntity'])]]></code>
<code><![CDATA[$this->table]]></code>
<code><![CDATA[isset($mapping['id']) && $mapping['id'] === true && ! $mapping['isOwningSide']]]></code>
<code><![CDATA[isset($mapping['orderBy']) && ! is_array($mapping['orderBy'])]]></code>
@@ -626,9 +625,6 @@
<code>$quotedColumnNames</code>
<code><![CDATA[$this->namespace . '\\' . $className]]></code>
</LessSpecificReturnStatement>
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<MoreSpecificReturnType>
<code>FieldMapping</code>
<code>class-string|null</code>
@@ -869,6 +865,9 @@
<MoreSpecificReturnType>
<code>class-string</code>
</MoreSpecificReturnType>
<NoValue>
<code>$metadata</code>
</NoValue>
<PossiblyNullArrayAccess>
<code><![CDATA[$this->tables[$tableName]]]></code>
<code><![CDATA[$this->tables[$tableName]]]></code>
@@ -1730,11 +1729,6 @@
<code>$sqlWalker</code>
</ParamNameMismatch>
</file>
<file src="lib/Doctrine/ORM/Query/AST/Node.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/AST/NullComparisonExpression.php">
<ParamNameMismatch>
<code>$sqlWalker</code>
@@ -1942,36 +1936,15 @@
<code>$parts</code>
</NonInvariantDocblockPropertyType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Base.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Comparison.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Composite.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<PossiblyInvalidCast>
<code>$part</code>
</PossiblyInvalidCast>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/From.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Func.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->arguments]]></code>
</LessSpecificReturnStatement>
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<MoreSpecificReturnType>
<code><![CDATA[list<mixed>]]></code>
</MoreSpecificReturnType>
@@ -1982,9 +1955,6 @@
</NonInvariantDocblockPropertyType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Join.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<PossiblyNullArgument>
<code><![CDATA[$this->conditionType]]></code>
</PossiblyNullArgument>
@@ -1994,16 +1964,6 @@
<code>$parts</code>
</NonInvariantDocblockPropertyType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Math.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/OrderBy.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
</file>
<file src="lib/Doctrine/ORM/Query/Expr/Orx.php">
<NonInvariantDocblockPropertyType>
<code>$allowedClasses</code>
@@ -2017,9 +1977,6 @@
</NonInvariantDocblockPropertyType>
</file>
<file src="lib/Doctrine/ORM/Query/Filter/SQLFilter.php">
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<MissingClosureParamType>
<code>$value</code>
</MissingClosureParamType>
@@ -2127,9 +2084,6 @@
<code>addNamedNativeQueryResultClassMapping</code>
<code>addNamedNativeQueryResultSetMapping</code>
</DeprecatedMethod>
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<PossiblyUndefinedArrayOffset>
<code><![CDATA[$associationMapping['joinColumns']]]></code>
<code><![CDATA[$associationMapping['joinColumns']]]></code>
@@ -2192,12 +2146,18 @@
<ImplicitToStringCast>
<code>$expr</code>
</ImplicitToStringCast>
<InvalidArrayOffset>
<code><![CDATA[$this->queryComponents[$expression]]]></code>
</InvalidArrayOffset>
<InvalidNullableReturnType>
<code>string</code>
</InvalidNullableReturnType>
<MoreSpecificImplementedParamType>
<code>$query</code>
</MoreSpecificImplementedParamType>
<NoValue>
<code>$expression</code>
</NoValue>
<PossiblyInvalidArgument>
<code><![CDATA[$aggExpression->pathExpression]]></code>
</PossiblyInvalidArgument>
@@ -2293,9 +2253,6 @@
<InvalidPropertyAssignmentValue>
<code>new ArrayCollection($parameters)</code>
</InvalidPropertyAssignmentValue>
<MethodSignatureMustProvideReturnType>
<code>__toString</code>
</MethodSignatureMustProvideReturnType>
<PossiblyFalseArgument>
<code>$spacePos</code>
<code>$spacePos</code>
@@ -2748,6 +2705,7 @@
<NoValue>
<code>$entityState</code>
<code>$entityState</code>
<code>$object</code>
</NoValue>
<PossiblyInvalidArgument>
<code>$value</code>

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Closure;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Exec\SingleSelectExecutor;
use Doctrine\ORM\Query\ParserResult;
@@ -12,6 +13,8 @@ use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
use ReflectionMethod;
use ReflectionProperty;
use Symfony\Component\VarExporter\Instantiator;
use Symfony\Component\VarExporter\VarExporter;
use function file_get_contents;
use function rtrim;
@@ -27,14 +30,18 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
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());
@@ -42,6 +49,27 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
$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) . ';');
},
];
}
public function testItSerializesParserResultWithAForwardCompatibleFormat(): void
{
$query = $this->_em
@@ -88,6 +116,25 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
yield '2.17.0' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_17_0.txt'), "\n")];
}
public function testSymfony44ProvidedData(): void
{
$sqlExecutor = $this->createMock(SingleSelectExecutor::class);
$resultSetMapping = $this->createMock(ResultSetMapping::class);
$parserResult = new ParserResult();
$parserResult->setSqlExecutor($sqlExecutor);
$parserResult->setResultSetMapping($resultSetMapping);
$parserResult->addParameterMapping('name', 0);
$exported = VarExporter::export($parserResult);
$unserialized = eval('return ' . $exported . ';');
$this->assertInstanceOf(ParserResult::class, $unserialized);
$this->assertInstanceOf(ResultSetMapping::class, $unserialized->getResultSetMapping());
$this->assertEquals(['name' => [0]], $unserialized->getParameterMappings());
$this->assertInstanceOf(SingleSelectExecutor::class, $unserialized->getSqlExecutor());
}
private static function parseQuery(Query $query): ParserResult
{
$r = new ReflectionMethod($query, 'parse');

View File

@@ -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.",
@@ -50,4 +54,9 @@ final class GH10661Test extends OrmTestCase
$ce
);
}
private function bootstrapValidator(bool $validatePropertyTypes = true): SchemaValidator
{
return new SchemaValidator($this->em, $validatePropertyTypes);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;
interface EntityStatus
{
}

View File

@@ -0,0 +1,50 @@
<?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;
/**
* @requires PHP >= 8.1
*/
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' 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
);
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,36 @@
<?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;
/**
* @Column(type="integer", enumType=StringEntityStatus::class)
*/
protected EntityStatus $status3;
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11037;
enum StringEntityStatus: string implements EntityStatus
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
}

View File

@@ -0,0 +1,36 @@
<?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;
/**
* @Column(type="string", enumType=StringEntityStatus::class)
*/
protected EntityStatus $status3;
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11072;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
/**
* @Entity
*/
class GH11072EntityAdvanced extends GH11072EntityBasic
{
/** @Column(type="json") */
public mixed $anything;
/** @Column(type="json") */
public true $alwaysTrue = true;
/** @Column(type="json") */
public false $alwaysFalse = false;
/** @Column(type="json") */
public null $alwaysNull = null;
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11072;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
/**
* @Entity
*/
class GH11072EntityBasic
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
* @var int
*/
public $id;
/** @Column(type="json") */
public string $jsonString = 'test';
/** @Column(type="json") */
public int $age = 99;
/** @Column(type="json") */
public float $score = 0.0;
/** @Column(type="json", nullable=true) */
public ?bool $trinary = null;
/** @Column(type="json") */
public array $metadata = [];
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11072;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @requires PHP >= 7.4
*/
final class GH11072Test extends OrmFunctionalTestCase
{
/** @var SchemaValidator */
private $validator;
protected function setUp(): void
{
$this->_em = $this->getTestEntityManager();
$this->validator = new SchemaValidator($this->_em);
}
public function testAcceptsSubsetOfBuiltinTypesWithoutErrors(): void
{
$class = $this->_em->getClassMetadata(GH11072EntityBasic::class);
$ce = $this->validator->validateClass($class);
self::assertSame([], $ce);
}
/**
* @requires PHP >= 8.2
*/
public function testAcceptsAdvancedSubsetOfBuiltinTypesWithoutErrors(): void
{
$class = $this->_em->getClassMetadata(GH11072EntityAdvanced::class);
$ce = $this->validator->validateClass($class);
self::assertSame([], $ce);
}
}