Compare commits

...

37 Commits

Author SHA1 Message Date
Alexander M. Turek 9c56071392 Adjust PHPBench mocks 2024-03-21 12:37:52 +01:00
Alexander M. Turek 0a1988b349 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)
2024-03-21 12:05:05 +01:00
Alexander M. Turek 1a5a4c674a Set column length explicitly (#11393) 2024-03-21 12:01:42 +01:00
Alexander M. Turek 95795c87a8 Add missing import 2024-03-21 10:38:59 +01:00
Alexander M. Turek db6e702088 Remove unused variable (#11391) 2024-03-21 10:32:55 +01:00
Grégoire Paris 4175edf311 Merge pull request #11387 from valkars/enum-reflection
Fixed proxy initialization for EnumReflectionProperty
2024-03-21 10:20:42 +01:00
Valentin Karnauhov 67ac5a82da Fixed proxy initialization for EnumReflectionProperty 2024-03-21 10:54:26 +02:00
Claudio Zizza e384978e0b Remove older versions from the docs (#11383)
To reduce Algolia operations and indexes older versions get removed
2024-03-20 23:35:25 +01:00
Thomas Landauer 5ccbc201bf [Documentation] Removing "Doctrine Mapping Types" ... (#11384)
... in favor of https://www.doctrine-project.org/projects/doctrine-dbal/en/3.8/reference/types.html#reference

Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/basic-mapping.html#doctrine-mapping-types

As announced in https://github.com/doctrine/dbal/pull/6336#issuecomment-2003720361 , the goal is to remove this duplicated type information from ORM and replace it with a link to DBAL.

In https://github.com/doctrine/dbal/pull/6341 , I'm adding any detail which I'm deleting here to the DBAL.
2024-03-20 23:34:10 +01:00
Benjamin Eberlei d15624f72f [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380) 2024-03-20 15:45:47 +01:00
Benjamin Eberlei 9d1a4973ae Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
* Improve lazy ghost performance by avoiding self-referencing closure.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>

* update baselien

---------

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2024-03-19 09:19:25 +01:00
Grégoire Paris 55c4845d57 Merge pull request #11379 from greg0ire/3.1.x
Merge 2.19.x up into 3.1.x
2024-03-18 21:07:12 +01:00
Grégoire Paris a38f473a92 Merge remote-tracking branch 'origin/2.19.x' into 3.1.x 2024-03-18 20:57:55 +01:00
Grégoire Paris 40a0964f06 Merge pull request #11289 from themasch/reproduce-issue-11154-composite-key-eager-fetch-one
Do not use batch loading for collections with composite identifier
2024-03-18 20:12:56 +01:00
Grégoire Paris 08a9e60ed0 Remove outdated git metadata files (#11362)
Some of it seems related to the previous documentation build system,
some of it seems related to IntelliJ.
2024-03-17 23:06:30 +01:00
Benjamin Eberlei 3e3c023c95 Switch join columns around, otherwise index doesnt match 2024-03-17 19:50:56 +01:00
Benjamin Eberlei 5e6d5c06a9 Key on fk 2024-03-17 19:43:26 +01:00
Benjamin Eberlei 1622b7877d Fix entities and mapping. 2024-03-17 18:02:11 +01:00
Benjamin Eberlei 80aae2796d Merge pull request #11373 from kaznovac/patch-3
Minor code style fix in AbstractRemoteControl
2024-03-17 17:20:01 +01:00
Marko Kaznovac 528ef40fc4 Minor code style fix in AbstractRemoteControl 2024-03-17 15:55:54 +01:00
Grégoire Paris 7178b9d6b7 Merge pull request #11370 from greg0ire/forgotten-array-access
Avoid another occurrence of ArrayAccess
2024-03-17 10:42:16 +01:00
Grégoire Paris 8a14eee67a Avoid another occurrence of ArrayAccess 2024-03-17 09:38:54 +01:00
Grégoire Paris c5315f86fb Merge pull request #11368 from greg0ire/address-deprecation
Avoid array access
2024-03-17 09:02:38 +01:00
Grégoire Paris 5820bb8f49 Avoid array access
It is deprecated.
2024-03-16 23:37:00 +01:00
Benjamin Eberlei 820a0da4c1 Do not schedule batch loading for target classes with composite identifier. 2024-03-16 23:05:28 +01:00
Benjamin Eberlei fcd02b1ee2 Cleanup tests not to use model sets. 2024-03-16 23:04:57 +01:00
Grégoire Paris b0d07ffaba Merge pull request #11363 from greg0ire/3.1.x
Merge 2.19.x up into 3.1.x
2024-03-16 21:48:21 +01:00
Grégoire Paris 196d3a6996 Merge remote-tracking branch 'origin/2.19.x' into 3.1.x 2024-03-16 21:38:16 +01:00
Grégoire Paris abcad6fa45 Merge pull request #11090 from dbannik/2.17.x-failed-getting-entity-with-fetch-eager
[2.17.x] Failed getting entity with fetch eager property
2024-03-16 21:23:13 +01:00
Benjamin Eberlei 1b6cf58a1a Rename tables to avoid pg related illegal table name 2024-03-16 21:08:30 +01:00
Benjamin Eberlei 6501890ab5 Static analysis enforces the extra isset() even though that just masks no sense. 2024-03-16 20:48:15 +01:00
Benjamin Eberlei e399d21fb3 Simplify condition, improve comment on this edge case. 2024-03-16 20:41:24 +01:00
Benjamin Eberlei 16f355f0cc Remove tests for already working case as they add no value other than exploration, and we only need the regression test. 2024-03-16 20:31:09 +01:00
Grégoire Paris 94d45a036f Merge pull request #11347 from greg0ire/remove-orphan
Remove guides-specific markup
2024-03-11 21:08:16 +01:00
Grégoire Paris 9acca2252f Remove guides-specific markup
doctrine/rst-parser does not appear to support orphan metadata yet, and
renders it verbatim on the website.

Let's move this to the CI job.
2024-03-11 20:31:22 +01:00
Mark Schmale 8d4718f875 provides a test case for github issue 11154
After 2.17 (some?) EAGER fetched OneToMany associations stopped working, if they have multiple join columns. Loads for these associations will trigger a `MessingPositionalParameter` exception "Positional parameter at index 1 does not have a bound value".

This test case should reproduce this issue, so it can be fixed.
2024-02-22 10:58:50 +01:00
Dmitry Bannik e5e3166747 #11090 - Fix obtaining an identifier in cases where the hydration has not yet fully completed on eagerLoadCollections 2024-02-16 12:57:23 +03:00
30 changed files with 589 additions and 136 deletions
-36
View File
@@ -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
}
]
}
+5
View File
@@ -40,5 +40,10 @@ jobs:
with:
dependency-versions: "highest"
- name: "Add orphan metadata where needed"
run: |
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
+1 -1
View File
@@ -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",
-4
View File
@@ -1,4 +0,0 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea
-3
View File
@@ -1,3 +0,0 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git
+5 -43
View File
@@ -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::
-2
View File
@@ -1,5 +1,3 @@
:orphan:
Installation
============
-2
View File
@@ -1,5 +1,3 @@
:orphan:
.. toc::
.. tocheader:: Tutorials
+1 -6
View File
@@ -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>
+7 -9
View File
@@ -216,11 +216,11 @@ EOPHP;
*/
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),
@@ -234,11 +234,11 @@ EOPHP;
$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));
}
};
}
@@ -283,9 +283,7 @@ EOPHP;
$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])) {
+15 -3
View File
@@ -2581,9 +2581,9 @@ class UnitOfWork implements PropertyChangedListener
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();
}
@@ -2662,7 +2662,19 @@ class UnitOfWork implements PropertyChangedListener
foreach ($found as $targetValue) {
$sourceEntity = $targetProperty->getValue($targetValue);
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]->joinColumns)) {
// case where the hydration $targetValue itself has not yet fully completed, for example
// in case a bi-directional association is being hydrated and deferring eager loading is
// not possible due to subclassing.
$data = $this->getOriginalEntityData($targetValue);
$id = [];
foreach ($targetClass->associationMappings[$mappedBy]->joinColumns as $joinColumn) {
$id[] = $data[$joinColumn->name];
}
} else {
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
}
$idHash = implode(' ', $id);
if ($mapping->indexBy !== null) {
+8 -17
View File
@@ -4,8 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Performance\Mock;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
/**
@@ -13,22 +12,14 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
*/
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)();
}
}
@@ -57,7 +57,7 @@ class NonProxyLoadingEntityManager implements EntityManagerInterface
public function getUnitOfWork(): UnitOfWork
{
return new NonProxyLoadingUnitOfWork();
return new NonProxyLoadingUnitOfWork($this);
}
public function getCache(): Cache|null
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Performance\Mock;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\UnitOfWork;
/**
@@ -11,15 +12,17 @@ use Doctrine\ORM\UnitOfWork;
*/
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));
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'abstract_fetch_eager_remote_control')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['mobile' => 'MobileRemoteControl'])]
abstract class AbstractRemoteControl
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
/** @var Collection<User> */
#[ORM\OneToMany(targetEntity: User::class, mappedBy: 'remoteControl', fetch: 'EAGER')]
public Collection $users;
public function __construct(string $name)
{
$this->name = $name;
$this->users = new ArrayCollection();
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class MobileRemoteControl extends AbstractRemoteControl
{
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'abstract_fetch_eager_user')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\ManyToOne(targetEntity: AbstractRemoteControl::class, inversedBy: 'users')]
#[ORM\JoinColumn(nullable: false)]
public AbstractRemoteControl $remoteControl;
public function __construct(AbstractRemoteControl $control)
{
$this->remoteControl = $control;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\AbstractFetchEager\AbstractRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\MobileRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\User;
use Doctrine\Tests\OrmFunctionalTestCase;
final class AbstractFetchEagerTest extends OrmFunctionalTestCase
{
public function testWithAbstractFetchEager(): void
{
$this->createSchemaForModels(
AbstractRemoteControl::class,
User::class,
);
$control = new MobileRemoteControl('smart');
$user = new User($control);
$entityManage = $this->getEntityManager();
$entityManage->persist($control);
$entityManage->persist($user);
$entityManage->flush();
$entityManage->clear();
$user = $entityManage->find(User::class, $user->id);
self::assertNotNull($user);
self::assertEquals('smart', $user->remoteControl->name);
self::assertTrue($user->remoteControl->users->contains($user));
}
}
@@ -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());
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table('gh11149_eager_product')]
class EagerProduct
{
#[ORM\Id]
#[ORM\Column]
public int $id;
/** @var Collection<string, EagerProductTranslation> */
#[ORM\OneToMany(
targetEntity: EagerProductTranslation::class,
mappedBy: 'product',
fetch: 'EAGER',
indexBy: 'locale_code',
)]
public Collection $translations;
public function __construct(int $id)
{
$this->id = $id;
$this->translations = new ArrayCollection();
}
}
@@ -0,0 +1,31 @@
<?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]
private int $id;
#[ORM\ManyToOne(inversedBy: 'translations')]
#[ORM\JoinColumn(nullable: false)]
public EagerProduct $product;
#[ORM\ManyToOne]
#[ORM\JoinColumn(name: 'locale_code', referencedColumnName: 'code', nullable: false)]
public Locale $locale;
public function __construct(int $id, EagerProduct $product, Locale $locale)
{
$this->id = $id;
$this->product = $product;
$this->locale = $locale;
}
}
@@ -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);
}
}
@@ -0,0 +1,21 @@
<?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(length: 5)]
public string $code;
public function __construct(string $code)
{
$this->code = $code;
}
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
#[Entity]
class GH11386EntityCart
{
#[Id]
#[GeneratedValue]
#[Column]
private int|null $id = null;
#[Column]
private int|null $amount = null;
#[OneToOne(inversedBy: 'cart', cascade: ['persist', 'remove'], orphanRemoval: true)]
private GH11386EntityCustomer|null $customer = null;
public function getId(): int|null
{
return $this->id;
}
public function getAmount(): int|null
{
return $this->amount;
}
public function setAmount(int $amount): static
{
$this->amount = $amount;
return $this;
}
public function getCustomer(): GH11386EntityCustomer|null
{
return $this->customer;
}
public function setCustomer(GH11386EntityCustomer|null $customer): self
{
$this->customer = $customer;
return $this;
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
#[Entity]
class GH11386EntityCustomer
{
#[Id]
#[GeneratedValue]
#[Column]
private int|null $id = null;
#[Column]
private string|null $name = null;
#[Column(type: 'smallint', nullable: true, enumType: GH11386EnumType::class, options: ['unsigned' => true])]
private GH11386EnumType|null $type = null;
#[OneToOne(mappedBy: 'customer')]
private GH11386EntityCart|null $cart = null;
public function getId(): int|null
{
return $this->id;
}
public function getName(): string|null
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getType(): GH11386EnumType|null
{
return $this->type;
}
public function setType(GH11386EnumType $type): static
{
$this->type = $type;
return $this;
}
public function getCart(): GH11386EntityCart|null
{
return $this->cart;
}
public function setCart(GH11386EntityCart|null $cart): self
{
// unset the owning side of the relation if necessary
if ($cart === null && $this->cart !== null) {
$this->cart->setCustomer(null);
}
// set the owning side of the relation if necessary
if ($cart !== null && $cart->getCustomer() !== $this) {
$cart->setCustomer($this);
}
$this->cart = $cart;
return $this;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
enum GH11386EnumType: int
{
case MALE = 1;
case FEMALE = 2;
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH11386Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH11386EntityCart::class,
GH11386EntityCustomer::class,
);
}
public function testInitializeClonedProxy(): void
{
$cart = new GH11386EntityCart();
$cart->setAmount(1000);
$customer = new GH11386EntityCustomer();
$customer->setName('John Doe')
->setType(GH11386EnumType::MALE)
->setCart($cart);
$this->_em->persist($cart);
$this->_em->flush();
$this->_em->clear();
$cart = $this->_em->find(GH11386EntityCart::class, 1);
$customer = clone $cart->getCustomer();
self::assertEquals('John Doe', $customer->getName());
}
}
+3 -4
View File
@@ -62,9 +62,8 @@ class ProxyFactoryTest extends OrmTestCase
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();
@@ -74,8 +73,8 @@ class ProxyFactoryTest extends OrmTestCase
$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();