Skip to content

Commit

Permalink
Merge branch '2.19.x' into 3.1.x
Browse files Browse the repository at this point in the history
* 2.19.x:
  Remove unused variable (doctrine#11391)
  [Documentation] Removing "Doctrine Mapping Types" ... (doctrine#11384)
  [doctrineGH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (doctrine#11380)
  Improve lazy ghost performance by avoiding self-referencing closure. (doctrine#11376)
  • Loading branch information
derrabus committed Mar 21, 2024
2 parents 4175edf + db6e702 commit a80c1cd
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 62 deletions.
48 changes: 5 additions & 43 deletions docs/en/reference/basic-mapping.rst
Expand Up @@ -228,50 +228,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
Doctrine Mapping Types
----------------------

The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
The ``type`` option used in the ``@Column`` accepts any of the
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
or :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.

As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:

- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
object.
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps a SQL CLOB to a PHP string.
- ``object``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps a SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream

A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
you are using.

.. note::

Expand Down
7 changes: 1 addition & 6 deletions psalm-baseline.xml
Expand Up @@ -760,13 +760,8 @@
<code><![CDATA[$autoGenerate > 4]]></code>
</TypeDoesNotContainType>
<UndefinedMethod>
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
$initializer($object, $proxy);
}, $skippedProperties)]]></code>
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
</UndefinedMethod>
<UndefinedVariable>
<code><![CDATA[$proxy]]></code>
</UndefinedVariable>
<UnresolvableInclude>
<code><![CDATA[require $fileName]]></code>
</UnresolvableInclude>
Expand Down
14 changes: 6 additions & 8 deletions src/Proxy/ProxyFactory.php
Expand Up @@ -216,11 +216,11 @@ protected function skipClass(ClassMetadata $metadata): bool
*/
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
{
return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($original);
$entity = $entityPersister->loadById($identifier, $original);
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($proxy);
$original = $entityPersister->loadById($identifier);

if ($entity === null) {
if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
$classMetadata->getName(),
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
Expand All @@ -238,7 +238,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
continue;
}

$property->setValue($proxy, $property->getValue($entity));
$property->setValue($proxy, $property->getValue($original));
}
};
}
Expand Down Expand Up @@ -283,9 +283,7 @@ private function getProxyFactory(string $className): Closure
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);

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

foreach ($identifierFields as $idField => $reflector) {
if (! isset($identifier[$idField])) {
Expand Down
2 changes: 1 addition & 1 deletion src/UnitOfWork.php
Expand Up @@ -2581,7 +2581,7 @@ public function createEntity(string $className, array $data, array &$hints = [])

if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite) {
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! isset($assoc['indexBy'])) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} else {
$this->loadCollection($pColl);
Expand Down
41 changes: 41 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH11149/EagerProduct.php
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

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

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table("gh11149_eager_product")
*/
class EagerProduct
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;

/**
* @ORM\OneToMany(
* targetEntity=EagerProductTranslation::class,
* mappedBy="product",
* fetch="EAGER",
* indexBy="locale_code"
* )
*
* @var Collection<string, EagerProductTranslation>
*/
public $translations;

public function __construct(int $id)
{
$this->id = $id;
$this->translations = new ArrayCollection();
}
}
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

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

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table("gh11149_eager_product_translation")
*/
class EagerProductTranslation
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;

/**
* @ORM\ManyToOne(targetEntity=EagerProduct::class, inversedBy="translations")
* @ORM\JoinColumn(nullable=false)
*
* @var EagerProduct
*/
public $product;

/**
* @ORM\ManyToOne(targetEntity=Locale::class)
* @ORM\JoinColumn(name="locale_code", referencedColumnName="code", nullable=false)
*
* @var Locale
*/
public $locale;

public function __construct($id, EagerProduct $product, Locale $locale)
{
$this->id = $id;
$this->product = $product;
$this->locale = $locale;
}
}
47 changes: 47 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH11149/GH11149Test.php
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

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

use Doctrine\ORM\PersistentCollection;
use Doctrine\Persistence\Proxy;
use Doctrine\Tests\OrmFunctionalTestCase;

class GH11149Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->setUpEntitySchema([
Locale::class,
EagerProduct::class,
EagerProductTranslation::class,
]);
}

public function testFetchEagerModeWithIndexBy(): void
{
// Load entities into database
$this->_em->persist($product = new EagerProduct(11149));
$this->_em->persist($locale = new Locale('fr_FR'));
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
$this->_em->flush();
$this->_em->clear();

// Fetch entity from database
$product = $this->_em->find(EagerProduct::class, 11149);

// Assert associated entity is loaded eagerly
static::assertInstanceOf(EagerProduct::class, $product);
static::assertInstanceOf(PersistentCollection::class, $product->translations);
static::assertTrue($product->translations->isInitialized());
static::assertCount(1, $product->translations);

// Assert associated entity is indexed by given property
$translation = $product->translations->get('fr_FR');
static::assertInstanceOf(EagerProductTranslation::class, $translation);
static::assertNotInstanceOf(Proxy::class, $translation);
}
}
27 changes: 27 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH11149/Locale.php
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

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

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table("gh11149_locale")
*/
class Locale
{
/**
* @ORM\Id
* @ORM\Column(type="string")
*
* @var string
*/
public $code;

public function __construct(string $code)
{
$this->code = $code;
}
}
7 changes: 3 additions & 4 deletions tests/Tests/ORM/Proxy/ProxyFactoryTest.php
Expand Up @@ -62,9 +62,8 @@ protected function setUp(): void
public function testReferenceProxyDelegatesLoadingToThePersister(): void
{
$identifier = ['id' => 42];
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->getMockBuilder(BasicEntityPersister::class)
->onlyMethods(['load'])
->onlyMethods(['loadById'])
->disableOriginalConstructor()
->getMock();

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

$persister
->expects(self::atLeastOnce())
->method('load')
->with(self::equalTo($identifier), self::isInstanceOf($proxyClass))
->method('loadById')
->with(self::equalTo($identifier))
->will(self::returnValue($proxy));

$proxy->getDescription();
Expand Down

0 comments on commit a80c1cd

Please sign in to comment.