Skip to content

Commit 73777d0

Browse files
committed
Merge branch '2.20.x' into 2.21.x
* 2.20.x: Introduce testNotListedValueInEnumArray Fix documentation for JoinColumn nullable (doctrine#11798) Ignore deprecations from doctrine/common Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2 parents e89b58a + c2a4932 commit 73777d0

File tree

6 files changed

+153
-8
lines changed

6 files changed

+153
-8
lines changed

docs/en/reference/association-mapping.rst

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,8 +1368,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.
13681368

13691369
Additionally, when using typed properties with Doctrine 2.9 or newer
13701370
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
1371-
associations as they will be set based on type. Also ``nullable``
1372-
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
1371+
associations as they will be set based on type. So that:
13731372

13741373
.. configuration-block::
13751374

@@ -1409,7 +1408,7 @@ Is essentially the same as following:
14091408
<?php
14101409
/** One Product has One Shipment. */
14111410
#[OneToOne(targetEntity: Shipment::class)]
1412-
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
1411+
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
14131412
private Shipment $shipment;
14141413
14151414
.. code-block:: annotation
@@ -1418,7 +1417,7 @@ Is essentially the same as following:
14181417
/**
14191418
* One Product has One Shipment.
14201419
* @OneToOne(targetEntity="Shipment")
1421-
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
1420+
* @JoinColumn(name="shipment_id", referencedColumnName="id")
14221421
*/
14231422
private Shipment $shipment;
14241423
@@ -1442,7 +1441,6 @@ Is essentially the same as following:
14421441
joinColumn:
14431442
name: shipment_id
14441443
referencedColumnName: id
1445-
nullable: false
14461444
14471445
If you accept these defaults, you can reduce the mapping code to a
14481446
minimum.

phpstan-baseline.neon

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,15 @@ parameters:
31443144
count: 1
31453145
path: src/Persisters/SqlValueVisitor.php
31463146

3147+
-
3148+
message: '''
3149+
#^Class Doctrine\\ORM\\Proxy\\Autoloader extends deprecated class Doctrine\\Common\\Proxy\\Autoloader\:
3150+
The Autoloader class is deprecated since doctrine/common 3\.5\.$#
3151+
'''
3152+
identifier: class.extendsDeprecatedClass
3153+
count: 1
3154+
path: src/Proxy/Autoloader.php
3155+
31473156
-
31483157
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns class\-string\<Doctrine\\Persistence\\Proxy\<T of object\>\>\|class\-string\<T of object\>\.$#'
31493158
identifier: return.type
@@ -3186,6 +3195,42 @@ parameters:
31863195
count: 1
31873196
path: src/Proxy/ProxyFactory.php
31883197

3198+
-
3199+
message: '''
3200+
#^Call to method __construct\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
3201+
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
3202+
'''
3203+
identifier: staticMethod.deprecatedClass
3204+
count: 1
3205+
path: src/Proxy/ProxyFactory.php
3206+
3207+
-
3208+
message: '''
3209+
#^Call to method generateProxyClasses\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
3210+
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
3211+
'''
3212+
identifier: staticMethod.deprecatedClass
3213+
count: 1
3214+
path: src/Proxy/ProxyFactory.php
3215+
3216+
-
3217+
message: '''
3218+
#^Call to method getProxy\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
3219+
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
3220+
'''
3221+
identifier: staticMethod.deprecatedClass
3222+
count: 1
3223+
path: src/Proxy/ProxyFactory.php
3224+
3225+
-
3226+
message: '''
3227+
#^Class Doctrine\\ORM\\Proxy\\ProxyFactory extends deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
3228+
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
3229+
'''
3230+
identifier: class.extendsDeprecatedClass
3231+
count: 1
3232+
path: src/Proxy/ProxyFactory.php
3233+
31893234
-
31903235
message: '#^Comparison operation "\<" between 0\|1\|2\|3\|4 and 0 is always false\.$#'
31913236
identifier: smaller.alwaysFalse
@@ -3198,6 +3243,15 @@ parameters:
31983243
count: 1
31993244
path: src/Proxy/ProxyFactory.php
32003245

3246+
-
3247+
message: '''
3248+
#^Instantiation of deprecated class Doctrine\\Common\\Proxy\\ProxyGenerator\:
3249+
The ProxyGenerator class is deprecated since doctrine/common 3\.5\.$#
3250+
'''
3251+
identifier: new.deprecated
3252+
count: 1
3253+
path: src/Proxy/ProxyFactory.php
3254+
32013255
-
32023256
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createCloner\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
32033257
identifier: throws.unusedType
@@ -3336,6 +3390,15 @@ parameters:
33363390
count: 1
33373391
path: src/Proxy/ProxyFactory.php
33383392

3393+
-
3394+
message: '''
3395+
#^Return type of method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:getProxy\(\) has typehint with deprecated interface Doctrine\\Common\\Proxy\\Proxy\:
3396+
The Proxy interface is deprecated since doctrine/common 3\.5\.$#
3397+
'''
3398+
identifier: return.deprecatedInterface
3399+
count: 1
3400+
path: src/Proxy/ProxyFactory.php
3401+
33393402
-
33403403
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Cache\\\\QueryCacheProfile'' and ''getResultCache'' will always evaluate to true\.$#'
33413404
identifier: function.alreadyNarrowedType

src/Internal/Hydration/SimpleObjectHydrator.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use function array_search;
1717
use function count;
1818
use function in_array;
19+
use function is_array;
1920
use function key;
2021
use function reset;
2122
use function sprintf;
@@ -143,14 +144,21 @@ protected function hydrateRowData(array $row, array &$result)
143144
}
144145

145146
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
146-
$originalValue = $value;
147+
$originalValue = $currentValue = $value;
147148
try {
148-
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
149+
if (! is_array($originalValue)) {
150+
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
151+
} else {
152+
$value = [];
153+
foreach ($originalValue as $i => $currentValue) {
154+
$value[$i] = $this->buildEnum($currentValue, $cacheKeyInfo['enumType']);
155+
}
156+
}
149157
} catch (ValueError $e) {
150158
throw MappingException::invalidEnumValue(
151159
$entityName,
152160
$cacheKeyInfo['fieldName'],
153-
(string) $originalValue,
161+
(string) $currentValue,
154162
$cacheKeyInfo['enumType'],
155163
$e
156164
);

src/Mapping/Driver/ReflectionBasedDriver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,13 @@ private function isRepeatedPropertyDeclaration(ReflectionProperty $property, Cla
3232
|| $metadata->isInheritedEmbeddedClass($property->name);
3333
}
3434

35+
/** @var class-string $declaringClass */
3536
$declaringClass = $property->class;
3637

38+
if ($this->isTransient($declaringClass)) {
39+
return isset($metadata->fieldMappings[$property->name]);
40+
}
41+
3742
if (
3843
isset($metadata->fieldMappings[$property->name]['declared'])
3944
&& $metadata->fieldMappings[$property->name]['declared'] === $declaringClass

tests/Tests/ORM/Functional/Ticket/GH10450Test.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public function classesThatOverrideFieldNames(): Generator
3232
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
3333
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
3434
}
35+
36+
public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
37+
{
38+
$em = $this->getTestEntityManager();
39+
40+
$metadata = $em->getClassMetadata(GH10450Cat::class);
41+
42+
self::assertArrayHasKey('id', $metadata->fieldMappings);
43+
}
3544
}
3645

3746
/**
@@ -179,3 +188,38 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
179188
*/
180189
protected $field;
181190
}
191+
192+
abstract class GH10450AbstractEntity
193+
{
194+
/**
195+
* @ORM\Column(type="integer")
196+
* @ORM\Id
197+
* @ORM\GeneratedValue
198+
*
199+
* @var int
200+
*/
201+
protected $id;
202+
}
203+
204+
/**
205+
* @ORM\Entity
206+
* @ORM\InheritanceType("SINGLE_TABLE")
207+
* @ORM\DiscriminatorMap({ "cat": "GH10450Cat" })
208+
* @ORM\DiscriminatorColumn(name="type")
209+
*/
210+
abstract class GH10450Animal extends GH10450AbstractEntity
211+
{
212+
/**
213+
* @ORM\Column(type="text", name="base")
214+
*
215+
* @var string
216+
*/
217+
private $field;
218+
}
219+
220+
/**
221+
* @ORM\Entity
222+
*/
223+
class GH10450Cat extends GH10450Animal
224+
{
225+
}

tests/Tests/ORM/Hydration/SimpleObjectHydratorTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
use Doctrine\DBAL\Types\Type as DBALType;
88
use Doctrine\ORM\Internal\Hydration\HydrationException;
99
use Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator;
10+
use Doctrine\ORM\Mapping\MappingException;
1011
use Doctrine\ORM\Query\ResultSetMapping;
1112
use Doctrine\Tests\DbalTypes\GH8565EmployeePayloadType;
1213
use Doctrine\Tests\DbalTypes\GH8565ManagerPayloadType;
1314
use Doctrine\Tests\Mocks\ArrayResultFactory;
1415
use Doctrine\Tests\Models\CMS\CmsAddress;
1516
use Doctrine\Tests\Models\Company\CompanyPerson;
17+
use Doctrine\Tests\Models\Enums\Scale;
18+
use Doctrine\Tests\Models\Enums\Unit;
1619
use Doctrine\Tests\Models\GH8565\GH8565Employee;
1720
use Doctrine\Tests\Models\GH8565\GH8565Manager;
1821
use Doctrine\Tests\Models\GH8565\GH8565Person;
@@ -155,4 +158,28 @@ public function testWrongValuesShouldNotBeConvertedToPhpValue(): void
155158
$result = $hydrator->hydrateAll($stmt, $rsm);
156159
self::assertEquals($result[0], $expectedEntity);
157160
}
161+
162+
/**
163+
* @requires PHP 8.1
164+
*/
165+
public function testNotListedValueInEnumArray(): void
166+
{
167+
$this->expectException(MappingException::class);
168+
$this->expectExceptionMessage('Case "unknown_case" is not listed in enum "Doctrine\Tests\Models\Enums\Unit"');
169+
$rsm = new ResultSetMapping();
170+
$rsm->addEntityResult(Scale::class, 's');
171+
$rsm->addFieldResult('s', 's__id', 'id');
172+
$rsm->addFieldResult('s', 's__supported_units', 'supportedUnits');
173+
$rsm->addEnumResult('s__supported_units', Unit::class);
174+
$resultSet = [
175+
[
176+
's__id' => 1,
177+
's__supported_units' => 'g,m,unknown_case',
178+
],
179+
];
180+
181+
$stmt = ArrayResultFactory::createFromArray($resultSet);
182+
$hydrator = new SimpleObjectHydrator($this->entityManager);
183+
$hydrator->hydrateAll($stmt, $rsm);
184+
}
158185
}

0 commit comments

Comments
 (0)