diff --git a/.doctrine-project.json b/.doctrine-project.json index 0eeb48f5899..f3a38fb4bdd 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -11,17 +11,23 @@ "slug": "latest", "upcoming": true }, + { + "name": "3.3", + "branchName": "3.3.x", + "slug": "3.3", + "upcoming": true + }, { "name": "3.2", "branchName": "3.2.x", "slug": "3.2", - "upcoming": true + "current": true }, { "name": "3.1", "branchName": "3.1.x", "slug": "3.1", - "current": true + "maintained": false }, { "name": "3.0", diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f9b2de81e24..59723f577fc 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -182,7 +182,7 @@ jobs: - "default" - "4@dev" mariadb-version: - - "10.9" + - "11.4" extension: - "mysqli" - "pdo_mysql" @@ -191,11 +191,11 @@ jobs: mariadb: image: "mariadb:${{ matrix.mariadb-version }}" env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: "doctrine_tests" + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes + MARIADB_DATABASE: "doctrine_tests" options: >- - --health-cmd "mysqladmin ping --silent" + --health-cmd "healthcheck.sh --connect --innodb_initialized" ports: - "3306:3306" diff --git a/README.md b/README.md index 70dceea1faa..1df322cf7e8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] | +| [4.0.x][4.0] | [3.3.x][3.3] | [3.2.x][3.2] | [2.20.x][2.20] | [2.19.x][2.19] | |:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:| -| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] | -| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | +| [![Build status][4.0 image]][4.0] | [![Build status][3.3 image]][3.3] | [![Build status][3.2 image]][3.2] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] | +| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | [
table]]>
{'discriminator-column'}]]>
- {'discriminator-map'}]]>
-
-
$usage,
'region' => $region,
]]]>
-
- {'discriminator-column'}]]>
- {'discriminator-map'}]]>
-
-
- getName() === 'embeddable']]>
- getName() === 'entity']]>
- getName() === 'mapped-superclass']]>
-
@@ -738,7 +723,9 @@
4]]>
-
+
diff --git a/src/Internal/Hydration/ObjectHydrator.php b/src/Internal/Hydration/ObjectHydrator.php
index d24323d8689..d0fc101f215 100644
--- a/src/Internal/Hydration/ObjectHydrator.php
+++ b/src/Internal/Hydration/ObjectHydrator.php
@@ -356,11 +356,15 @@ protected function hydrateRowData(array $row, array &$result): void
$parentObject = $this->resultPointers[$parentAlias];
} else {
// Parent object of relation not found, mark as not-fetched again
- $element = $this->getEntity($data, $dqlAlias);
+ if (isset($nonemptyComponents[$dqlAlias])) {
+ $element = $this->getEntity($data, $dqlAlias);
- // Update result pointer and provide initial fetch data for parent
- $this->resultPointers[$dqlAlias] = $element;
- $rowData['data'][$parentAlias][$relationField] = $element;
+ // Update result pointer and provide initial fetch data for parent
+ $this->resultPointers[$dqlAlias] = $element;
+ $rowData['data'][$parentAlias][$relationField] = $element;
+ } else {
+ $element = null;
+ }
// Mark as not-fetched again
unset($this->hints['fetched'][$parentAlias][$relationField]);
diff --git a/src/Mapping/Driver/DatabaseDriver.php b/src/Mapping/Driver/DatabaseDriver.php
index 4c09e2b01c9..19504d832fb 100644
--- a/src/Mapping/Driver/DatabaseDriver.php
+++ b/src/Mapping/Driver/DatabaseDriver.php
@@ -35,6 +35,8 @@
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database.
*
+ * @deprecated No replacement planned
+ *
* @link www.doctrine-project.org
*/
class DatabaseDriver implements MappingDriver
diff --git a/src/Mapping/Driver/XmlDriver.php b/src/Mapping/Driver/XmlDriver.php
index 63f234e513a..48f650576c1 100644
--- a/src/Mapping/Driver/XmlDriver.php
+++ b/src/Mapping/Driver/XmlDriver.php
@@ -38,6 +38,8 @@
* XmlDriver is a metadata driver that enables mapping through XML files.
*
* @link www.doctrine-project.org
+ *
+ * @template-extends FileDriver
*/
class XmlDriver extends FileDriver
{
@@ -78,7 +80,6 @@ public function __construct(
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$xmlRoot = $this->getElement($className);
- assert($xmlRoot instanceof SimpleXMLElement);
if ($xmlRoot->getName() === 'entity') {
if (isset($xmlRoot['repository-class'])) {
@@ -134,6 +135,7 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad
];
if (isset($discrColumn['options'])) {
+ assert($discrColumn['options'] instanceof SimpleXMLElement);
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
}
@@ -145,6 +147,7 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad
// Evaluate
if (isset($xmlRoot->{'discriminator-map'})) {
$map = [];
+ assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement);
foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
$map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
}
diff --git a/src/Mapping/InverseJoinColumn.php b/src/Mapping/InverseJoinColumn.php
index 89c8db0006b..2a77f3fc73b 100644
--- a/src/Mapping/InverseJoinColumn.php
+++ b/src/Mapping/InverseJoinColumn.php
@@ -2,7 +2,6 @@
declare(strict_types=1);
-
namespace Doctrine\ORM\Mapping;
use Attribute;
diff --git a/src/Persisters/Collection/OneToManyPersister.php b/src/Persisters/Collection/OneToManyPersister.php
index c62ea565ed5..0727b1f8a7e 100644
--- a/src/Persisters/Collection/OneToManyPersister.php
+++ b/src/Persisters/Collection/OneToManyPersister.php
@@ -8,6 +8,8 @@
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Types\Type;
+use Doctrine\ORM\EntityNotFoundException;
+use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Utility\PersisterHelper;
@@ -146,7 +148,11 @@ public function loadCriteria(PersistentCollection $collection, Criteria $criteri
throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.');
}
- /** @throws DBALException */
+ /**
+ * @throws DBALException
+ * @throws EntityNotFoundException
+ * @throws MappingException
+ */
private function deleteEntityCollection(PersistentCollection $collection): int
{
$mapping = $this->getMapping($collection);
@@ -166,6 +172,13 @@ private function deleteEntityCollection(PersistentCollection $collection): int
$statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform)
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
+ if ($targetClass->isInheritanceTypeSingleTable()) {
+ $discriminatorColumn = $targetClass->getDiscriminatorColumn();
+ $statement .= ' AND ' . $discriminatorColumn->name . ' = ?';
+ $parameters[] = $targetClass->discriminatorValue;
+ $types[] = $discriminatorColumn->type;
+ }
+
$numAffected = $this->conn->executeStatement($statement, $parameters, $types);
assert(is_int($numAffected));
diff --git a/src/Proxy/ProxyFactory.php b/src/Proxy/ProxyFactory.php
index 6184fa7811c..b2d114a6698 100644
--- a/src/Proxy/ProxyFactory.php
+++ b/src/Proxy/ProxyFactory.php
@@ -210,15 +210,14 @@ protected function skipClass(ClassMetadata $metadata): bool
/**
* Creates a closure capable of initializing a proxy
*
- * @return Closure(InternalProxy, InternalProxy):void
+ * @return Closure(InternalProxy, array):void
*
* @throws EntityNotFoundException
*/
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
{
- return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
- $identifier = $classMetadata->getIdentifierValues($proxy);
- $original = $entityPersister->loadById($identifier);
+ return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void {
+ $original = $entityPersister->loadById($identifier);
if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
@@ -234,7 +233,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
- if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
+ if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
continue;
}
@@ -283,7 +282,9 @@ 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($initializer, $skippedProperties);
+ $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
+ $initializer($object, $identifier);
+ }, $skippedProperties);
foreach ($identifierFields as $idField => $reflector) {
if (! isset($identifier[$idField])) {
@@ -386,12 +387,18 @@ private function generateUseLazyGhostTrait(ClassMetadata $class): string
$code = substr($code, 7 + (int) strpos($code, "\n{"));
$code = substr($code, 0, (int) strpos($code, "\n}"));
$code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait {
- initializeLazyObject as __load;
+ initializeLazyObject as private;
setLazyObjectAsInitialized as public __setInitialized;
isLazyObjectInitialized as private;
createLazyGhost as private;
resetLazyObject as private;
- }'), $code);
+ }
+
+ public function __load(): void
+ {
+ $this->initializeLazyObject();
+ }
+ '), $code);
return $code;
}
diff --git a/src/Query/Parser.php b/src/Query/Parser.php
index ade4bf347fe..e948f2c6b03 100644
--- a/src/Query/Parser.php
+++ b/src/Query/Parser.php
@@ -2563,7 +2563,10 @@ public function ArithmeticPrimary(): AST\Node|string
return new AST\ParenthesisExpression($expr);
}
- assert($this->lexer->lookahead !== null);
+ if ($this->lexer->lookahead === null) {
+ $this->syntaxError('ArithmeticPrimary');
+ }
+
switch ($this->lexer->lookahead->type) {
case TokenType::T_COALESCE:
case TokenType::T_NULLIF:
diff --git a/src/Query/SqlWalker.php b/src/Query/SqlWalker.php
index 004d29e773c..c6f98c12d50 100644
--- a/src/Query/SqlWalker.php
+++ b/src/Query/SqlWalker.php
@@ -911,7 +911,9 @@ public function walkJoinAssociationDeclaration(
}
}
- if ($relation->fetch === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
+ $fetchMode = $this->query->getHint('fetchMode')[$assoc->sourceEntity][$assoc->fieldName] ?? $relation->fetch;
+
+ if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
throw QueryException::eagerFetchJoinWithNotAllowed($assoc->sourceEntity, $assoc->fieldName);
}
diff --git a/tests/Tests/Models/ECommerce/ECommerceProduct2.php b/tests/Tests/Models/ECommerce/ECommerceProduct2.php
new file mode 100644
index 00000000000..1cbe939ef5b
--- /dev/null
+++ b/tests/Tests/Models/ECommerce/ECommerceProduct2.php
@@ -0,0 +1,46 @@
+id;
+ }
+
+ public function getName(): string|null
+ {
+ return $this->name;
+ }
+
+ public function __clone()
+ {
+ $this->id = null;
+ $this->name = 'Clone of ' . $this->name;
+ }
+}
diff --git a/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php b/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
index 88098c9c6da..b8d451097af 100644
--- a/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
+++ b/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
@@ -89,6 +89,14 @@ public function testSubselectFetchJoinWithNotAllowed(): void
$query->getResult();
}
+ public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEager(): void
+ {
+ $query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
+ $query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
+
+ $this->assertIsString($query->getSql());
+ }
+
public function testEagerFetchWithIterable(): void
{
$this->createOwnerWithChildren(2);
diff --git a/tests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.php b/tests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.php
index b2b3306ea1b..0cc8776ba50 100644
--- a/tests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.php
+++ b/tests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.php
@@ -58,7 +58,7 @@ protected function setUp(): void
public function testPersistUpdate(): void
{
// Considering case (a)
- $proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => 123]);
+ $proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => $this->user->getId()]);
$proxy->id = null;
$proxy->username = 'ocra';
diff --git a/tests/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Tests/ORM/Functional/ReferenceProxyTest.php
index 1a805d467c0..55f65956757 100644
--- a/tests/Tests/ORM/Functional/ReferenceProxyTest.php
+++ b/tests/Tests/ORM/Functional/ReferenceProxyTest.php
@@ -9,6 +9,7 @@
use Doctrine\ORM\Proxy\InternalProxy;
use Doctrine\Tests\Models\Company\CompanyAuction;
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
+use Doctrine\Tests\Models\ECommerce\ECommerceProduct2;
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
@@ -112,6 +113,24 @@ public function testCloneProxy(): void
self::assertFalse($entity->isCloned);
}
+ public function testCloneProxyWithResetId(): void
+ {
+ $id = $this->createProduct();
+
+ $entity = $this->_em->getReference(ECommerceProduct2::class, $id);
+ assert($entity instanceof ECommerceProduct2);
+
+ $clone = clone $entity;
+ assert($clone instanceof ECommerceProduct2);
+
+ self::assertEquals($id, $entity->getId());
+ self::assertEquals('Doctrine Cookbook', $entity->getName());
+
+ self::assertFalse($this->_em->contains($clone));
+ self::assertNull($clone->getId());
+ self::assertEquals('Clone of Doctrine Cookbook', $clone->getName());
+ }
+
#[Group('DDC-733')]
public function testInitializeProxy(): void
{
diff --git a/tests/Tests/ORM/Functional/Ticket/GH10889Test.php b/tests/Tests/ORM/Functional/Ticket/GH10889Test.php
new file mode 100644
index 00000000000..fe7d6e8c53c
--- /dev/null
+++ b/tests/Tests/ORM/Functional/Ticket/GH10889Test.php
@@ -0,0 +1,79 @@
+createSchemaForModels(
+ GH10889Person::class,
+ GH10889Company::class,
+ GH10889Resume::class,
+ );
+ }
+
+ public function testIssue(): void
+ {
+ $person = new GH10889Person();
+ $resume = new GH10889Resume($person, null);
+
+ $this->_em->persist($person);
+ $this->_em->persist($resume);
+ $this->_em->flush();
+ $this->_em->clear();
+
+ /** @var list $resumes */
+ $resumes = $this->_em
+ ->getRepository(GH10889Resume::class)
+ ->createQueryBuilder('resume')
+ ->leftJoin('resume.currentCompany', 'company')->addSelect('company')
+ ->getQuery()
+ ->getResult();
+
+ $this->assertArrayHasKey(0, $resumes);
+ $this->assertEquals(1, $resumes[0]->person->id);
+ $this->assertNull($resumes[0]->currentCompany);
+ }
+}
+
+#[ORM\Entity]
+class GH10889Person
+{
+ #[ORM\Id]
+ #[ORM\Column]
+ #[ORM\GeneratedValue]
+ public int|null $id = null;
+}
+
+#[ORM\Entity]
+class GH10889Company
+{
+ #[ORM\Id]
+ #[ORM\Column]
+ #[ORM\GeneratedValue]
+ public int|null $id = null;
+}
+
+#[ORM\Entity]
+class GH10889Resume
+{
+ public function __construct(
+ #[ORM\Id]
+ #[ORM\OneToOne]
+ public GH10889Person $person,
+ #[ORM\ManyToOne]
+ public GH10889Company|null $currentCompany,
+ ) {
+ }
+}
diff --git a/tests/Tests/ORM/Functional/Ticket/GH11487Test.php b/tests/Tests/ORM/Functional/Ticket/GH11487Test.php
new file mode 100644
index 00000000000..3e622611454
--- /dev/null
+++ b/tests/Tests/ORM/Functional/Ticket/GH11487Test.php
@@ -0,0 +1,34 @@
+expectException(QueryException::class);
+ $this->expectExceptionMessage('Syntax Error');
+ $this->_em->createQuery('UPDATE Doctrine\Tests\ORM\Functional\Ticket\TaxType t SET t.default =')->execute();
+ }
+}
+
+#[Entity]
+class TaxType
+{
+ #[Column]
+ #[Id]
+ #[GeneratedValue]
+ public int|null $id = null;
+
+ #[Column]
+ public bool $default = false;
+}
diff --git a/tests/Tests/ORM/Functional/Ticket/GH11500Test.php b/tests/Tests/ORM/Functional/Ticket/GH11500Test.php
new file mode 100644
index 00000000000..b2f8123fabd
--- /dev/null
+++ b/tests/Tests/ORM/Functional/Ticket/GH11500Test.php
@@ -0,0 +1,109 @@
+setUpEntitySchema([
+ GH11500AbstractTestEntity::class,
+ GH11500TestEntityOne::class,
+ GH11500TestEntityTwo::class,
+ GH11500TestEntityHolder::class,
+ ]);
+ }
+
+ /** @throws ORMException */
+ public function testDeleteOneToManyCollectionWithSingleTableInheritance(): void
+ {
+ $testEntityOne = new GH11500TestEntityOne();
+ $testEntityTwo = new GH11500TestEntityTwo();
+ $testEntityHolder = new GH11500TestEntityHolder();
+
+ $testEntityOne->testEntityHolder = $testEntityHolder;
+ $testEntityHolder->testEntityOnes->add($testEntityOne);
+
+ $testEntityTwo->testEntityHolder = $testEntityHolder;
+ $testEntityHolder->testEntityTwos->add($testEntityTwo);
+
+ $em = $this->getEntityManager();
+ $em->persist($testEntityOne);
+ $em->persist($testEntityTwo);
+ $em->persist($testEntityHolder);
+ $em->flush();
+
+ $testEntityTwosBeforeRemovalOfTestEntityOnes = $testEntityHolder->testEntityTwos->toArray();
+
+ $testEntityHolder->testEntityOnes = new ArrayCollection();
+ $em->persist($testEntityHolder);
+ $em->flush();
+ $em->refresh($testEntityHolder);
+
+ static::assertEmpty($testEntityHolder->testEntityOnes->toArray(), 'All records should have been deleted');
+ static::assertEquals($testEntityTwosBeforeRemovalOfTestEntityOnes, $testEntityHolder->testEntityTwos->toArray(), 'Different Entity\'s records should not have been deleted');
+ }
+}
+
+
+
+#[ORM\Entity]
+#[ORM\Table(name: 'one_to_many_single_table_inheritance_test_entities')]
+#[ORM\InheritanceType('SINGLE_TABLE')]
+#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
+#[ORM\DiscriminatorMap(['test_entity_one' => 'GH11500TestEntityOne', 'test_entity_two' => 'GH11500TestEntityTwo'])]
+class GH11500AbstractTestEntity
+{
+ #[ORM\Id]
+ #[ORM\Column]
+ #[ORM\GeneratedValue]
+ public int|null $id = null;
+}
+
+
+#[ORM\Entity]
+class GH11500TestEntityOne extends GH11500AbstractTestEntity
+{
+ #[ORM\ManyToOne(inversedBy:'testEntityOnes')]
+ #[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
+ public GH11500TestEntityHolder|null $testEntityHolder = null;
+}
+
+#[ORM\Entity]
+class GH11500TestEntityTwo extends GH11500AbstractTestEntity
+{
+ #[ORM\ManyToOne(inversedBy:'testEntityTwos')]
+ #[ORM\JoinColumn(name:'test_entity_holder_id', referencedColumnName:'id')]
+ public GH11500TestEntityHolder|null $testEntityHolder = null;
+}
+
+#[ORM\Entity]
+class GH11500TestEntityHolder
+{
+ #[ORM\Id]
+ #[ORM\Column]
+ #[ORM\GeneratedValue]
+ public int|null $id = null;
+
+ #[ORM\OneToMany(targetEntity: 'GH11500TestEntityOne', mappedBy: 'testEntityHolder', orphanRemoval: true)]
+ public Collection $testEntityOnes;
+
+ #[ORM\OneToMany(targetEntity: 'GH11500TestEntityTwo', mappedBy: 'testEntityHolder', orphanRemoval: true)]
+ public Collection $testEntityTwos;
+
+ public function __construct()
+ {
+ $this->testEntityOnes = new ArrayCollection();
+ $this->testEntityTwos = new ArrayCollection();
+ }
+}