mirror of
https://github.com/doctrine/orm.git
synced 2026-04-25 07:28:04 +02:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c56071392 | |||
| 0a1988b349 | |||
| 1a5a4c674a | |||
| 95795c87a8 | |||
| db6e702088 | |||
| 4175edf311 | |||
| 67ac5a82da | |||
| e384978e0b | |||
| 5ccbc201bf | |||
| d15624f72f | |||
| 9d1a4973ae | |||
| 55c4845d57 | |||
| a38f473a92 | |||
| 40a0964f06 | |||
| 08a9e60ed0 | |||
| 3e3c023c95 | |||
| 5e6d5c06a9 | |||
| 1622b7877d | |||
| 80aae2796d | |||
| 528ef40fc4 | |||
| 7178b9d6b7 | |||
| 8a14eee67a | |||
| c5315f86fb | |||
| 5820bb8f49 | |||
| 820a0da4c1 | |||
| fcd02b1ee2 | |||
| b0d07ffaba | |||
| 196d3a6996 | |||
| abcad6fa45 | |||
| 1b6cf58a1a | |||
| 6501890ab5 | |||
| e399d21fb3 | |||
| 16f355f0cc | |||
| 94d45a036f | |||
| 9acca2252f | |||
| 8d4718f875 | |||
| e5e3166747 |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
en/_exts/configurationblock.pyc
|
||||
build
|
||||
en/_build
|
||||
.idea
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "en/_theme"]
|
||||
path = en/_theme
|
||||
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
@@ -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::
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
|
||||
+1
-6
@@ -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>
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user