Skip to content

Commit fd2bab9

Browse files
committed
Merge branch '2.12.x' into 3.0.x
* 2.12.x: Allow using Enum from different namespace than Entity (#9384) Corrected ORM version and added missing dependency (#9386) PHPStan 1.4.0 (#9385) [GH-9380] Bugfix: Delegate ReflectionEnumProperty::getAttributes(). (#9381) Support enum cases as parameters (#9373) Add detach as of list cascade-all operations (#9357)
2 parents 7d8134c + 07f1c4e commit fd2bab9

17 files changed

+213
-38
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"doctrine/annotations": "^1.13",
4242
"doctrine/coding-standard": "^9.0",
4343
"phpbench/phpbench": "^1.0",
44-
"phpstan/phpstan": "1.3.3",
44+
"phpstan/phpstan": "1.4.0",
4545
"phpunit/phpunit": "^9.5",
4646
"squizlabs/php_codesniffer": "3.6.2",
4747
"symfony/cache": "^4.4 || ^5.4 || ^6.0",

docs/en/reference/xml-mapping.rst

+1
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ specified by their respective tags:
694694
- ``<cascade-merge />``
695695
- ``<cascade-remove />``
696696
- ``<cascade-refresh />``
697+
- ``<cascade-detach />``
697698

698699
Join Column Element
699700
~~~~~~~~~~~~~~~~~~~

docs/en/tutorials/getting-started.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ that directory with the following contents:
8181

8282
{
8383
"require": {
84-
"doctrine/orm": "^2.10.2",
85-
"doctrine/dbal": "^3.1.1",
86-
"symfony/cache": "^5.3"
84+
"doctrine/orm": "^2.11.0",
85+
"doctrine/dbal": "^3.2",
86+
"doctrine/annotations": "1.13.2",
87+
"symfony/cache": "^5.4"
8788
},
8889
"autoload": {
8990
"psr-0": {"": "src/"}

lib/Doctrine/ORM/AbstractQuery.php

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\ORM;
66

7+
use BackedEnum;
78
use Countable;
89
use Doctrine\Common\Cache\Psr6\CacheAdapter;
910
use Doctrine\Common\Collections\ArrayCollection;
@@ -420,6 +421,10 @@ public function processParameterValue($value)
420421
return $value->name;
421422
}
422423

424+
if ($value instanceof BackedEnum) {
425+
return $value->value;
426+
}
427+
423428
if (! is_object($value)) {
424429
return $value;
425430
}

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array
14991499
! isset($mapping['type'])
15001500
&& ($type instanceof ReflectionNamedType)
15011501
) {
1502-
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName(), false)) {
1502+
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
15031503
$mapping['enumType'] = $type->getName();
15041504

15051505
$reflection = new ReflectionEnum($type->getName());

lib/Doctrine/ORM/Mapping/ReflectionEnumProperty.php

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public function __construct(ReflectionProperty $originalReflectionProperty, stri
2929
{
3030
$this->originalReflectionProperty = $originalReflectionProperty;
3131
$this->enumType = $enumType;
32+
33+
parent::__construct(
34+
$originalReflectionProperty->getDeclaringClass()->getName(),
35+
$originalReflectionProperty->getName()
36+
);
3237
}
3338

3439
/**

lib/Doctrine/ORM/Query/ParameterTypeInferer.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\ORM\Query;
66

7+
use BackedEnum;
78
use DateInterval;
89
use DateTimeImmutable;
910
use DateTimeInterface;
@@ -54,8 +55,19 @@ public static function inferType($value)
5455
return Types::DATEINTERVAL;
5556
}
5657

58+
if ($value instanceof BackedEnum) {
59+
return is_int($value->value)
60+
? Types::INTEGER
61+
: Types::STRING;
62+
}
63+
5764
if (is_array($value)) {
58-
return is_int(current($value))
65+
$firstValue = current($value);
66+
if ($firstValue instanceof BackedEnum) {
67+
$firstValue = $firstValue->value;
68+
}
69+
70+
return is_int($firstValue)
5971
? Connection::PARAM_INT_ARRAY
6072
: Connection::PARAM_STR_ARRAY;
6173
}

phpcs.xml.dist

+6
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,16 @@
268268

269269
<rule ref="Generic.WhiteSpace.ScopeIndent.Incorrect">
270270
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
271+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/AccessLevel.php</exclude-pattern>
272+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/City.php</exclude-pattern>
271273
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
274+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/UserStatus.php</exclude-pattern>
272275
</rule>
273276
<rule ref="Generic.WhiteSpace.ScopeIndent.IncorrectExact">
274277
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
278+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/AccessLevel.php</exclude-pattern>
279+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/City.php</exclude-pattern>
275280
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
281+
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/UserStatus.php</exclude-pattern>
276282
</rule>
277283
</ruleset>

phpstan-baseline.neon

-15
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,6 @@ parameters:
255255
count: 1
256256
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
257257

258-
-
259-
message: "#^Array \\(array\\<class\\-string, object\\>\\) does not accept key string\\.$#"
260-
count: 1
261-
path: lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php
262-
263258
-
264259
message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:mapEmbedded\\(\\)\\.$#"
265260
count: 1
@@ -550,11 +545,6 @@ parameters:
550545
count: 1
551546
path: lib/Doctrine/ORM/Persisters/Collection/OneToManyPersister.php
552547

553-
-
554-
message: "#^Array \\(array\\<class\\-string, string\\>\\) does not accept key string\\.$#"
555-
count: 1
556-
path: lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
557-
558548
-
559549
message: "#^Parameter \\#3 \\$hints of method Doctrine\\\\ORM\\\\Internal\\\\Hydration\\\\AbstractHydrator\\:\\:hydrateAll\\(\\) expects array\\<string, string\\>, array\\<string, Doctrine\\\\ORM\\\\PersistentCollection\\|true\\> given\\.$#"
560550
count: 1
@@ -825,11 +815,6 @@ parameters:
825815
count: 1
826816
path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
827817

828-
-
829-
message: "#^Array \\(array\\<string, array\\<int, string\\>\\|string\\>\\) does not accept key int\\.$#"
830-
count: 1
831-
path: lib/Doctrine/ORM/Query/SqlWalker.php
832-
833818
-
834819
message: "#^Call to function is_string\\(\\) with Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node will always evaluate to false\\.$#"
835820
count: 2

psalm.xml

+6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@
6767
<file name="lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php"/>
6868
</errorLevel>
6969
</MissingDependency>
70+
<NoInterfaceProperties>
71+
<errorLevel type="suppress">
72+
<!-- see https://github.com/vimeo/psalm/issues/7364 -->
73+
<referencedClass name="BackedEnum"/>
74+
</errorLevel>
75+
</NoInterfaceProperties>
7076
<ParadoxicalCondition>
7177
<errorLevel type="suppress">
7278
<!-- See https://github.com/vimeo/psalm/issues/3381 -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Enums;
6+
7+
enum AccessLevel: int
8+
{
9+
case Admin = 1;
10+
case User = 2;
11+
case Guests = 3;
12+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Enums;
6+
7+
enum City: string
8+
{
9+
case Paris = 'Paris';
10+
case Cannes = 'Cannes';
11+
case StJulien = 'St Julien';
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Enums;
6+
7+
enum UserStatus: string
8+
{
9+
case Active = 'active';
10+
case Inactive = 'inactive';
11+
}

tests/Doctrine/Tests/ORM/Functional/EnumTest.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\Tests\ORM\Functional;
66

7+
use Doctrine\ORM\Mapping\Column;
78
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
89
use Doctrine\ORM\Mapping\MappingException;
910
use Doctrine\ORM\Tools\SchemaTool;
@@ -22,11 +23,11 @@ class EnumTest extends OrmFunctionalTestCase
2223
{
2324
public function setUp(): void
2425
{
26+
parent::setUp();
27+
2528
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums']));
2629
$this->_schemaTool = new SchemaTool($this->_em);
2730

28-
parent::setUp();
29-
3031
if ($this->isSecondLevelCacheEnabled) {
3132
$this->markTestSkipped();
3233
}
@@ -101,4 +102,15 @@ public function provideCardClasses(): array
101102
TypedCard::class => [TypedCard::class],
102103
];
103104
}
105+
106+
public function testItAllowsReadingAttributes(): void
107+
{
108+
$metadata = $this->_em->getClassMetadata(Card::class);
109+
$property = $metadata->getReflectionProperty('suit');
110+
111+
$attributes = $property->getAttributes();
112+
113+
$this->assertCount(1, $attributes);
114+
$this->assertEquals(Column::class, $attributes[0]->getName());
115+
}
104116
}

tests/Doctrine/Tests/ORM/Functional/QueryTest.php

+62
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Doctrine\Tests\Models\CMS\CmsArticle;
1717
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
1818
use Doctrine\Tests\Models\CMS\CmsUser;
19+
use Doctrine\Tests\Models\Enums\AccessLevel;
20+
use Doctrine\Tests\Models\Enums\UserStatus;
1921
use Doctrine\Tests\OrmFunctionalTestCase;
2022
use Exception;
2123

@@ -165,6 +167,66 @@ public function testInvalidInputParameterThrowsException(): void
165167
->getSingleResult();
166168
}
167169

170+
/**
171+
* @requires PHP 8.1
172+
*/
173+
public function testUseStringEnumCaseAsParameter(): void
174+
{
175+
$user = new CmsUser();
176+
$user->name = 'John';
177+
$user->username = 'john';
178+
$user->status = 'inactive';
179+
$this->_em->persist($user);
180+
181+
$user = new CmsUser();
182+
$user->name = 'Jane';
183+
$user->username = 'jane';
184+
$user->status = 'active';
185+
$this->_em->persist($user);
186+
187+
unset($user);
188+
189+
$this->_em->flush();
190+
$this->_em->clear();
191+
192+
$result = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u WHERE u.status = :status')
193+
->setParameter('status', UserStatus::Active)
194+
->getResult();
195+
196+
self::assertCount(1, $result);
197+
self::assertSame('jane', $result[0]->username);
198+
}
199+
200+
/**
201+
* @requires PHP 8.1
202+
*/
203+
public function testUseIntegerEnumCaseAsParameter(): void
204+
{
205+
$user = new CmsUser();
206+
$user->name = 'John';
207+
$user->username = 'john';
208+
$user->status = '1';
209+
$this->_em->persist($user);
210+
211+
$user = new CmsUser();
212+
$user->name = 'Jane';
213+
$user->username = 'jane';
214+
$user->status = '2';
215+
$this->_em->persist($user);
216+
217+
unset($user);
218+
219+
$this->_em->flush();
220+
$this->_em->clear();
221+
222+
$result = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u WHERE u.status = :status')
223+
->setParameter('status', AccessLevel::User)
224+
->getResult();
225+
226+
self::assertCount(1, $result);
227+
self::assertSame('jane', $result[0]->username);
228+
}
229+
168230
public function testSetParameters(): void
169231
{
170232
$parameters = new ArrayCollection();

tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php

+25-15
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,36 @@
1111
use Doctrine\DBAL\ParameterType;
1212
use Doctrine\DBAL\Types\Types;
1313
use Doctrine\ORM\Query\ParameterTypeInferer;
14+
use Doctrine\Tests\Models\Enums\AccessLevel;
15+
use Doctrine\Tests\Models\Enums\UserStatus;
1416
use Doctrine\Tests\OrmTestCase;
17+
use Generator;
18+
19+
use const PHP_VERSION_ID;
1520

1621
class ParameterTypeInfererTest extends OrmTestCase
1722
{
18-
/** @psalm-return list<array{mixed, int|string}> */
19-
public function providerParameterTypeInferer(): array
23+
/** @psalm-return Generator<string, array{mixed, (int|string)}> */
24+
public function providerParameterTypeInferer(): Generator
2025
{
21-
return [
22-
[1, Types::INTEGER],
23-
['bar', ParameterType::STRING],
24-
['1', ParameterType::STRING],
25-
[new DateTime(), Types::DATETIME_MUTABLE],
26-
[new DateTimeImmutable(), Types::DATETIME_IMMUTABLE],
27-
[new DateInterval('P1D'), Types::DATEINTERVAL],
28-
[[2], Connection::PARAM_INT_ARRAY],
29-
[['foo'], Connection::PARAM_STR_ARRAY],
30-
[['1','2'], Connection::PARAM_STR_ARRAY],
31-
[[], Connection::PARAM_STR_ARRAY],
32-
[true, Types::BOOLEAN],
33-
];
26+
yield 'integer' => [1, Types::INTEGER];
27+
yield 'string' => ['bar', ParameterType::STRING];
28+
yield 'numeric_string' => ['1', ParameterType::STRING];
29+
yield 'datetime_object' => [new DateTime(), Types::DATETIME_MUTABLE];
30+
yield 'datetime_immutable_object' => [new DateTimeImmutable(), Types::DATETIME_IMMUTABLE];
31+
yield 'date_interval_object' => [new DateInterval('P1D'), Types::DATEINTERVAL];
32+
yield 'array_of_int' => [[2], Connection::PARAM_INT_ARRAY];
33+
yield 'array_of_string' => [['foo'], Connection::PARAM_STR_ARRAY];
34+
yield 'array_of_numeric_string' => [['1', '2'], Connection::PARAM_STR_ARRAY];
35+
yield 'empty_array' => [[], Connection::PARAM_STR_ARRAY];
36+
yield 'boolean' => [true, Types::BOOLEAN];
37+
38+
if (PHP_VERSION_ID >= 80100) {
39+
yield 'int_backed_enum' => [AccessLevel::Admin, Types::INTEGER];
40+
yield 'string_backed_enum' => [UserStatus::Active, Types::STRING];
41+
yield 'array_of_int_backed_enum' => [[AccessLevel::Admin], Connection::PARAM_INT_ARRAY];
42+
yield 'array_of_string_backed_enum' => [[UserStatus::Active], Connection::PARAM_STR_ARRAY];
43+
}
3444
}
3545

3646
/**

0 commit comments

Comments
 (0)