Skip to content

Commit

Permalink
Merge branch '3.1.x' into 3.2.x
Browse files Browse the repository at this point in the history
* 3.1.x:
  Adjust PHPBench mocks
  Set column length explicitly (doctrine#11393)
  Add missing import
  Remove unused variable (doctrine#11391)
  Fixed proxy initialization for EnumReflectionProperty
  Remove older versions from the docs (doctrine#11383)
  [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)
  Remove outdated git metadata files (doctrine#11362)
  Switch join columns around, otherwise index doesnt match
  Key on fk
  Fix entities and mapping.
  Minor code style fix in AbstractRemoteControl
  Do not schedule batch loading for target classes with composite identifier.
  Cleanup tests not to use model sets.
  provides a test case for github issue 11154
  • Loading branch information
derrabus committed Mar 21, 2024
2 parents 69f51cc + 9c56071 commit 4672d28
Show file tree
Hide file tree
Showing 23 changed files with 461 additions and 131 deletions.
36 changes: 0 additions & 36 deletions .doctrine-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,42 +94,6 @@
"branchName": "2.10.x",
"slug": "2.10",
"maintained": false
},
{
"name": "2.9",
"branchName": "2.9.x",
"slug": "2.9",
"maintained": false
},
{
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"maintained": false
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"maintained": false
},
{
"name": "2.6",
"branchName": "2.6",
"slug": "2.6",
"maintained": false
},
{
"name": "2.5",
"branchName": "2.5",
"slug": "2.5",
"maintained": false
},
{
"name": "2.4",
"branchName": "2.4",
"slug": "2.4",
"maintained": false
}
]
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"doctrine/persistence": "^3.3.1",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
"symfony/var-exporter": "^6.3.9 || ^7.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
Expand Down
4 changes: 0 additions & 4 deletions docs/.gitignore

This file was deleted.

3 changes: 0 additions & 3 deletions docs/.gitmodules

This file was deleted.

48 changes: 5 additions & 43 deletions docs/en/reference/basic-mapping.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
16 changes: 7 additions & 9 deletions src/Proxy/ProxyFactory.php
Original file line number Diff line number Diff line change
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 @@ -234,11 +234,11 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
$class = $entityPersister->getClassMetadata();

foreach ($class->getReflectionProperties() as $property) {
if (! $property || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
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
4 changes: 2 additions & 2 deletions src/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -2581,9 +2581,9 @@ 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()) {
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} elseif (($isIteration && $assoc->isOneToMany()) || $assoc->isManyToMany()) {
} else {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
Expand Down
25 changes: 8 additions & 17 deletions tests/Performance/Mock/NonLoadingPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,22 @@

namespace Doctrine\Performance\Mock;

use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;

/**
* A persister that doesn't actually load given objects
*/
class NonLoadingPersister extends BasicEntityPersister
{
public function __construct()
{
public function __construct(
ClassMetadata $class,
) {
$this->class = $class;
}

/**
* {@inheritDoc}
*/
public function load(
array $criteria,
object|null $entity = null,
AssociationMapping|null $assoc = null,
array $hints = [],
LockMode|int|null $lockMode = null,
int|null $limit = null,
array|null $orderBy = null,
): object|null {
return $entity;
public function loadById(array $identifier, object|null $entity = null): object|null
{
return $entity ?? new ($this->class->name)();
}
}
2 changes: 1 addition & 1 deletion tests/Performance/Mock/NonProxyLoadingEntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function getClassMetadata(string $className): ClassMetadata

public function getUnitOfWork(): UnitOfWork
{
return new NonProxyLoadingUnitOfWork();
return new NonProxyLoadingUnitOfWork($this);
}

public function getCache(): Cache|null
Expand Down
13 changes: 8 additions & 5 deletions tests/Performance/Mock/NonProxyLoadingUnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@

namespace Doctrine\Performance\Mock;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\UnitOfWork;

/**
* An unit of work mock that prevents lazy-loading of proxies
*/
class NonProxyLoadingUnitOfWork extends UnitOfWork
{
private NonLoadingPersister $entityPersister;
/** @var array<class-string, NonLoadingPersister> */
private array $entityPersisters = [];

public function __construct()
{
$this->entityPersister = new NonLoadingPersister();
public function __construct(
private EntityManagerInterface $entityManager,
) {
}

public function getEntityPersister(string $entityName): NonLoadingPersister
{
return $this->entityPersister;
return $this->entityPersisters[$entityName]
??= new NonLoadingPersister($this->entityManager->getClassMetadata($entityName));
}
}
43 changes: 43 additions & 0 deletions tests/Tests/Models/EagerFetchedCompositeOneToMany/RootEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;

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

#[ORM\Entity]
#[ORM\Table(name: 'eager_composite_join_root')]
class RootEntity
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
private int|null $id = null;

#[ORM\Id]
#[ORM\Column(type: 'string', nullable: false, name: 'other_key', length: 42)]
private string $otherKey;

/** @var Collection<int, SecondLevel> */
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
private Collection $secondLevel;

public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
}

public function getId(): int|null
{
return $this->id;
}

public function getOtherKey(): string
{
return $this->otherKey;
}
}
38 changes: 38 additions & 0 deletions tests/Tests/Models/EagerFetchedCompositeOneToMany/SecondLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'eager_composite_join_second_level')]
#[ORM\Index(name: 'root_other_key_idx', columns: ['root_other_key', 'root_id'])]
class SecondLevel
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
private int|null $upperId;

#[ORM\Id]
#[ORM\Column(type: 'string', nullable: false, name: 'other_key')]
private string $otherKey;

#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'secondLevel')]
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
private RootEntity $root;

public function __construct(RootEntity $upper)
{
$this->upperId = $upper->getId();
$this->otherKey = $upper->getOtherKey();
$this->root = $upper;
}

public function getId(): int|null
{
return $this->id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional;

use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
use Doctrine\Tests\OrmFunctionalTestCase;

final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
{
/** @ticket 11154 */
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
{
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);

$a1 = new RootEntity(1, 'A');

$this->_em->persist($a1);
$this->_em->flush();

$this->_em->clear();

self::assertCount(1, $this->_em->getRepository(RootEntity::class)->findAll());
}
}
Loading

0 comments on commit 4672d28

Please sign in to comment.