Skip to content

Commit 0a1988b

Browse files
committed
Merge branch '2.19.x' into 3.1.x
* 2.19.x: Set column length explicitly (#11393) Add missing import Remove unused variable (#11391) [Documentation] Removing "Doctrine Mapping Types" ... (#11384) [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380) Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
2 parents 4175edf + 1a5a4c6 commit 0a1988b

File tree

10 files changed

+149
-63
lines changed

10 files changed

+149
-63
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"doctrine/persistence": "^3.3.1",
3434
"psr/cache": "^1 || ^2 || ^3",
3535
"symfony/console": "^5.4 || ^6.0 || ^7.0",
36-
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
36+
"symfony/var-exporter": "^6.3.9 || ^7.0"
3737
},
3838
"require-dev": {
3939
"doctrine/coding-standard": "^12.0",

docs/en/reference/basic-mapping.rst

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -228,50 +228,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
228228
Doctrine Mapping Types
229229
----------------------
230230

231-
The ``type`` option used in the ``@Column`` accepts any of the existing
232-
Doctrine types or even your own custom types. A Doctrine type defines
231+
The ``type`` option used in the ``@Column`` accepts any of the
232+
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
233+
or :doc:`your own custom mapping types
234+
<../cookbook/custom-mapping-types>`. A Doctrine type defines
233235
the conversion between PHP and SQL types, independent from the database vendor
234-
you are using. All Mapping Types that ship with Doctrine are fully portable
235-
between the supported database systems.
236-
237-
As an example, the Doctrine Mapping Type ``string`` defines the
238-
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
239-
depending on the RDBMS brand). Here is a quick overview of the
240-
built-in mapping types:
241-
242-
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
243-
- ``integer``: Type that maps a SQL INT to a PHP integer.
244-
- ``smallint``: Type that maps a database SMALLINT to a PHP
245-
integer.
246-
- ``bigint``: Type that maps a database BIGINT to a PHP string.
247-
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
248-
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
249-
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
250-
object.
251-
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
252-
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
253-
DateTime object.
254-
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
255-
DateTime object with timezone.
256-
- ``text``: Type that maps a SQL CLOB to a PHP string.
257-
- ``object``: Type that maps a SQL CLOB to a PHP object using
258-
``serialize()`` and ``unserialize()``
259-
- ``array``: Type that maps a SQL CLOB to a PHP array using
260-
``serialize()`` and ``unserialize()``
261-
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
262-
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
263-
Only use this type if you are sure that your values cannot contain a ",".
264-
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
265-
``json_encode()`` and ``json_decode()``
266-
- ``float``: Type that maps a SQL Float (Double Precision) to a
267-
PHP double. *IMPORTANT*: Works only with locale settings that use
268-
decimal points as separator.
269-
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
270-
varchar but uses a specific type if the platform supports it.
271-
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
272-
273-
A cookbook article shows how to define :doc:`your own custom mapping types
274-
<../cookbook/custom-mapping-types>`.
236+
you are using.
275237

276238
.. note::
277239

psalm-baseline.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -760,13 +760,8 @@
760760
<code><![CDATA[$autoGenerate > 4]]></code>
761761
</TypeDoesNotContainType>
762762
<UndefinedMethod>
763-
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
764-
$initializer($object, $proxy);
765-
}, $skippedProperties)]]></code>
763+
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
766764
</UndefinedMethod>
767-
<UndefinedVariable>
768-
<code><![CDATA[$proxy]]></code>
769-
</UndefinedVariable>
770765
<UnresolvableInclude>
771766
<code><![CDATA[require $fileName]]></code>
772767
</UnresolvableInclude>

src/Proxy/ProxyFactory.php

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ protected function skipClass(ClassMetadata $metadata): bool
216216
*/
217217
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
218218
{
219-
return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
220-
$identifier = $classMetadata->getIdentifierValues($original);
221-
$entity = $entityPersister->loadById($identifier, $original);
219+
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
220+
$identifier = $classMetadata->getIdentifierValues($proxy);
221+
$original = $entityPersister->loadById($identifier);
222222

223-
if ($entity === null) {
223+
if ($original === null) {
224224
throw EntityNotFoundException::fromClassNameAndIdentifier(
225225
$classMetadata->getName(),
226226
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
@@ -238,7 +238,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
238238
continue;
239239
}
240240

241-
$property->setValue($proxy, $property->getValue($entity));
241+
$property->setValue($proxy, $property->getValue($original));
242242
}
243243
};
244244
}
@@ -283,9 +283,7 @@ private function getProxyFactory(string $className): Closure
283283
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
284284

285285
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
286-
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
287-
$initializer($object, $proxy);
288-
}, $skippedProperties);
286+
$proxy = self::createLazyGhost($initializer, $skippedProperties);
289287

290288
foreach ($identifierFields as $idField => $reflector) {
291289
if (! isset($identifier[$idField])) {

src/UnitOfWork.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,7 @@ public function createEntity(string $className, array $data, array &$hints = [])
25812581

25822582
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
25832583
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
2584-
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite) {
2584+
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
25852585
$this->scheduleCollectionForBatchLoading($pColl, $class);
25862586
} else {
25872587
$this->loadCollection($pColl);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\ORM\Mapping as ORM;
10+
11+
#[ORM\Entity]
12+
#[ORM\Table('gh11149_eager_product')]
13+
class EagerProduct
14+
{
15+
#[ORM\Id]
16+
#[ORM\Column]
17+
public int $id;
18+
19+
/** @var Collection<string, EagerProductTranslation> */
20+
#[ORM\OneToMany(
21+
targetEntity: EagerProductTranslation::class,
22+
mappedBy: 'product',
23+
fetch: 'EAGER',
24+
indexBy: 'locale_code',
25+
)]
26+
public Collection $translations;
27+
28+
public function __construct(int $id)
29+
{
30+
$this->id = $id;
31+
$this->translations = new ArrayCollection();
32+
}
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
#[ORM\Entity]
10+
#[ORM\Table('gh11149_eager_product_translation')]
11+
class EagerProductTranslation
12+
{
13+
#[ORM\Id]
14+
#[ORM\Column]
15+
private int $id;
16+
17+
#[ORM\ManyToOne(inversedBy: 'translations')]
18+
#[ORM\JoinColumn(nullable: false)]
19+
public EagerProduct $product;
20+
21+
#[ORM\ManyToOne]
22+
#[ORM\JoinColumn(name: 'locale_code', referencedColumnName: 'code', nullable: false)]
23+
public Locale $locale;
24+
25+
public function __construct(int $id, EagerProduct $product, Locale $locale)
26+
{
27+
$this->id = $id;
28+
$this->product = $product;
29+
$this->locale = $locale;
30+
}
31+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
6+
7+
use Doctrine\ORM\PersistentCollection;
8+
use Doctrine\Persistence\Proxy;
9+
use Doctrine\Tests\OrmFunctionalTestCase;
10+
11+
class GH11149Test extends OrmFunctionalTestCase
12+
{
13+
protected function setUp(): void
14+
{
15+
parent::setUp();
16+
17+
$this->setUpEntitySchema([
18+
Locale::class,
19+
EagerProduct::class,
20+
EagerProductTranslation::class,
21+
]);
22+
}
23+
24+
public function testFetchEagerModeWithIndexBy(): void
25+
{
26+
// Load entities into database
27+
$this->_em->persist($product = new EagerProduct(11149));
28+
$this->_em->persist($locale = new Locale('fr_FR'));
29+
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
30+
$this->_em->flush();
31+
$this->_em->clear();
32+
33+
// Fetch entity from database
34+
$product = $this->_em->find(EagerProduct::class, 11149);
35+
36+
// Assert associated entity is loaded eagerly
37+
static::assertInstanceOf(EagerProduct::class, $product);
38+
static::assertInstanceOf(PersistentCollection::class, $product->translations);
39+
static::assertTrue($product->translations->isInitialized());
40+
static::assertCount(1, $product->translations);
41+
42+
// Assert associated entity is indexed by given property
43+
$translation = $product->translations->get('fr_FR');
44+
static::assertInstanceOf(EagerProductTranslation::class, $translation);
45+
static::assertNotInstanceOf(Proxy::class, $translation);
46+
}
47+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
#[ORM\Entity]
10+
#[ORM\Table('gh11149_locale')]
11+
class Locale
12+
{
13+
#[ORM\Id]
14+
#[ORM\Column(length: 5)]
15+
public string $code;
16+
17+
public function __construct(string $code)
18+
{
19+
$this->code = $code;
20+
}
21+
}

tests/Tests/ORM/Proxy/ProxyFactoryTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,8 @@ protected function setUp(): void
6262
public function testReferenceProxyDelegatesLoadingToThePersister(): void
6363
{
6464
$identifier = ['id' => 42];
65-
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
6665
$persister = $this->getMockBuilder(BasicEntityPersister::class)
67-
->onlyMethods(['load'])
66+
->onlyMethods(['loadById'])
6867
->disableOriginalConstructor()
6968
->getMock();
7069

@@ -74,8 +73,8 @@ public function testReferenceProxyDelegatesLoadingToThePersister(): void
7473

7574
$persister
7675
->expects(self::atLeastOnce())
77-
->method('load')
78-
->with(self::equalTo($identifier), self::isInstanceOf($proxyClass))
76+
->method('loadById')
77+
->with(self::equalTo($identifier))
7978
->will(self::returnValue($proxy));
8079

8180
$proxy->getDescription();

0 commit comments

Comments
 (0)