Merge pull request #10473 from mpdude/mapped-superclass-resolve-to-many

Allow to-many associations on mapped superclasses w/ ResolveTargetEntityListener
This commit is contained in:
Grégoire Paris
2023-03-01 08:16:07 +01:00
committed by GitHub
41 changed files with 372 additions and 45 deletions

View File

@@ -15,22 +15,32 @@ is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance).
(through Single Table Inheritance or Class Table Inheritance). They
are not query-able, and need not have an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. Also, a mapped superclass
need not have an ``#[Id]`` property.
only for entity classes inheriting from it. That implies that a
mapped superclass cannot be the ``targetEntity`` in associations.
In other words, a mapped superclass can use unidirectional One-To-One
and Many-To-One associations where it is the owning side.
Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For further
support of inheritance, the single or joined table inheritance features
have to be used.
.. note::
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the
mapped superclass is only used in exactly one entity at the moment.
For further support of inheritance, the single or
joined table inheritance features have to be used.
One-To-Many associations are not generally possible on a mapped
superclass, since they require the "many" side to hold the foreign
key.
It is, however, possible to use the :doc:```ResolveTargetEntityListener`` <cookbook/resolve-target-entity-listener>`
to replace references to a mapped superclass with an entity class at runtime.
As long as there is only one entity subclass inheriting from the mapped
superclass and all references to the mapped superclass are resolved to that
entity class at runtime, the mapped superclass *can* use One-To-Many associations
and be named as the ``targetEntity`` on the owning sides.
.. warning::

View File

@@ -270,6 +270,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->validateIdentifier();
$class->validateAssociations();
$this->validateAssociationTargets($class);
$class->validateLifecycleCallbacks($this->getReflectionService());
// verify inheritance
@@ -303,6 +304,16 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
private function validateAssociationTargets(ClassMetadata $class): void
{
foreach ($class->getAssociationMappings() as $mapping) {
$targetEntity = $mapping['targetEntity'];
if ($this->driver->isTransient($targetEntity) || $this->peekIfIsMappedSuperclass($targetEntity)) {
throw MappingException::associationTargetIsNotAnEntity($targetEntity, $class->name, $mapping['fieldName']);
}
}
}
/**
* {@inheritDoc}
*/
@@ -468,10 +479,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
// this is the case <=> ! isset($mapping['inherited']).
if (! isset($mapping['inherited'])) {
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}

View File

@@ -847,6 +847,15 @@ class MappingException extends ORMException
return new self('The target-entity ' . $targetEntity . " cannot be found in '" . $sourceEntity . '#' . $associationName . "'.");
}
/**
* @param class-string $targetEntity
* @param class-string $sourceEntity
*/
public static function associationTargetIsNotAnEntity(string $targetEntity, string $sourceEntity, string $associationName): self
{
return new self(sprintf('The target entity class %s specified for %s::$%s is not an entity class.', $targetEntity, $sourceEntity, $associationName));
}
/**
* @param string[] $cascades
* @param string $className

View File

@@ -7,6 +7,7 @@ namespace Doctrine\Tests\Models\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\Entity;
@@ -29,6 +30,7 @@ use Doctrine\ORM\Mapping\Table;
* 3 = "Bar"
* })
*/
#[Entity]
abstract class Attraction
{
/**
@@ -109,4 +111,8 @@ abstract class Attraction
$this->infos->add($info);
}
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -7,6 +7,7 @@ namespace Doctrine\Tests\Models\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -21,6 +22,7 @@ use Doctrine\ORM\Mapping\Table;
* @Table("cache_state")
* @Cache("NONSTRICT_READ_WRITE")
*/
#[Entity]
class State
{
/**
@@ -105,4 +107,8 @@ class State
{
$this->cities[] = $city;
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -8,6 +8,7 @@ use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -23,6 +24,7 @@ use Doctrine\ORM\Mapping\Table;
* @Entity
* @Table("cache_travel")
*/
#[Entity]
class Travel
{
/**
@@ -104,4 +106,8 @@ class Travel
{
return $this->createdAt;
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\Models\DDC3579;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -67,4 +68,8 @@ class DDC3579Group
{
return $this->admins;
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\Models\DDC5934;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\ClassMetadata;
/** @ORM\Entity() */
#[ORM\Entity]
@@ -23,4 +24,8 @@ class DDC5934Member
{
$this->contracts = new ArrayCollection();
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -4,12 +4,14 @@ declare(strict_types=1);
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
/** @Entity */
#[Entity]
class DDC964Address
{
/**
@@ -100,4 +102,8 @@ class DDC964Address
{
$this->street = $street;
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -12,6 +13,7 @@ use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToMany;
/** @Entity */
#[Entity]
class DDC964Group
{
/**
@@ -60,4 +62,8 @@ class DDC964Group
{
return $this->users;
}
public static function loadMetadata(ClassMetadata $metadata): void
{
}
}

View File

@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
use Doctrine\Tests\OrmTestCase;
class GH10473Test extends OrmTestCase
{
public function testMappedSuperclassAssociationsCanBeResolvedToEntities(): void
{
$em = $this->getTestEntityManager();
$resolveTargetEntity = new ResolveTargetEntityListener();
$resolveTargetEntity->addResolveTargetEntity(
GH10473BaseUser::class,
GH10473UserImplementation::class,
[]
);
$em->getEventManager()->addEventSubscriber($resolveTargetEntity);
$userMetadata = $em->getClassMetadata(GH10473UserImplementation::class);
self::assertFalse($userMetadata->isMappedSuperclass);
self::assertTrue($userMetadata->isInheritanceTypeNone());
$socialMediaAccountsMapping = $userMetadata->getAssociationMapping('socialMediaAccounts');
self::assertArrayNotHasKey('inherited', $socialMediaAccountsMapping);
self::assertTrue((bool) ($socialMediaAccountsMapping['type'] & ClassMetadata::TO_MANY));
self::assertFalse($socialMediaAccountsMapping['isOwningSide']);
self::assertSame(GH10473SocialMediaAccount::class, $socialMediaAccountsMapping['targetEntity']);
self::assertSame('user', $socialMediaAccountsMapping['mappedBy']);
$createdByMapping = $userMetadata->getAssociationMapping('createdBy');
self::assertArrayNotHasKey('inherited', $createdByMapping);
self::assertTrue((bool) ($createdByMapping['type'] & ClassMetadata::TO_ONE));
self::assertTrue($createdByMapping['isOwningSide']);
self::assertSame(GH10473UserImplementation::class, $createdByMapping['targetEntity']);
self::assertSame('createdUsers', $createdByMapping['inversedBy']);
$createdUsersMapping = $userMetadata->getAssociationMapping('createdUsers');
self::assertArrayNotHasKey('inherited', $createdUsersMapping);
self::assertTrue((bool) ($createdUsersMapping['type'] & ClassMetadata::TO_MANY));
self::assertFalse($createdUsersMapping['isOwningSide']);
self::assertSame(GH10473UserImplementation::class, $createdUsersMapping['targetEntity']);
self::assertSame('createdBy', $createdUsersMapping['mappedBy']);
$socialMediaAccountMetadata = $em->getClassMetadata(GH10473SocialMediaAccount::class);
self::assertFalse($socialMediaAccountMetadata->isMappedSuperclass);
self::assertTrue($socialMediaAccountMetadata->isInheritanceTypeNone());
$userMapping = $socialMediaAccountMetadata->getAssociationMapping('user');
self::assertArrayNotHasKey('inherited', $userMapping);
self::assertTrue((bool) ($userMapping['type'] & ClassMetadata::TO_ONE));
self::assertTrue($userMapping['isOwningSide']);
self::assertSame(GH10473UserImplementation::class, $userMapping['targetEntity']);
self::assertSame('socialMediaAccounts', $userMapping['inversedBy']);
}
}
/**
* @ORM\MappedSuperclass
*/
abstract class GH10473BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;
/**
* @ORM\OneToMany(targetEntity="GH10473SocialMediaAccount", mappedBy="user")
*
* @var Collection
*/
private $socialMediaAccounts;
/**
* @ORM\ManyToOne(targetEntity="GH10473BaseUser", inversedBy="createdUsers")
*
* @var GH10473BaseUser
*/
private $createdBy;
/**
* @ORM\OneToMany(targetEntity="GH10473BaseUser", mappedBy="createdBy")
*
* @var Collection
*/
private $createdUsers;
}
/**
* @ORM\Entity
*/
class GH10473SocialMediaAccount
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="GH10473BaseUser", inversedBy="socialMediaAccounts")
*
* @var GH10473BaseUser
*/
private $user;
}
/**
* @ORM\Entity
*/
class GH10473UserImplementation extends GH10473BaseUser
{
}

View File

@@ -175,11 +175,10 @@ class AnnotationDriverTest extends MappingDriverTestCase
$this->expectException(MappingException::class);
$this->expectExceptionMessage(
'It is illegal to put an inverse side one-to-many or many-to-many association on ' .
"mapped superclass 'Doctrine\Tests\ORM\Mapping\InvalidMappedSuperClass#users'"
'The target entity class Doctrine\Tests\ORM\Mapping\InvalidMappedSuperClass specified for Doctrine\Tests\ORM\Mapping\InvalidMappedSuperClass::$selfWhatever is not an entity class.'
);
$factory->getMetadataFor(UsingInvalidMappedSuperClass::class);
$factory->getMetadataFor(InvalidMappedSuperClass::class);
}
/** @group DDC-1050 */
@@ -309,22 +308,10 @@ class ColumnWithoutType
class InvalidMappedSuperClass
{
/**
* @psalm-var Collection<int, CmsUser>
* @ManyToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsUser", mappedBy="invalid")
* @psalm-var Collection<int, self>
* @ManyToMany(targetEntity="InvalidMappedSuperClass", mappedBy="invalid")
*/
private $users;
}
/** @Entity */
class UsingInvalidMappedSuperClass extends InvalidMappedSuperClass
{
/**
* @var int
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
private $selfWhatever;
}
/**

View File

@@ -315,6 +315,7 @@ class MappedSuperclassBase
private $transient;
}
/** @Entity */
class MappedSuperclassRelated1
{
}

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Cache\Attraction" />
</doctrine-mapping>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Cache\State" />
</doctrine-mapping>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Cache\Travel" />
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC117\DDC117Article"/>
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC117\DDC117Editor" />
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC3579\DDC3579Group" />
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC5934\DDC5934Member" />
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC964\DDC964Address" />
</doctrine-mapping>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC964\DDC964Group" />
</doctrine-mapping>

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\Cache\Attraction:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\Cache\State:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\Cache\Travel:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\DDC3579\DDC3579Group:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\DDC5934\DDC5934Member:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\DDC964\DDC964Address:
type: entity

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\Models\DDC964\DDC964Group:
type: entity

View File

@@ -15,6 +15,7 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
@@ -25,8 +26,9 @@ use Doctrine\Tests\OrmTestCase;
use Doctrine\Tests\TestUtil;
use Symfony\Component\Yaml\Parser;
use function array_filter;
use function array_values;
use function count;
use function current;
use function file_get_contents;
use function glob;
use function is_array;
@@ -106,7 +108,9 @@ abstract class ClassMetadataExporterTestCase extends OrmTestCase
$metadataDriver = $this->createMetadataDriver($type, __DIR__ . '/' . $type);
$em = $this->createEntityManager($metadataDriver);
$cmf = $this->createClassMetadataFactory($em, $type);
$metadata = $cmf->getAllMetadata();
$metadata = array_values(array_filter($cmf->getAllMetadata(), static function (ClassMetadata $class): bool {
return $class->name === User::class;
}));
$metadata[0]->name = ExportedUser::class;
@@ -142,15 +146,15 @@ abstract class ClassMetadataExporterTestCase extends OrmTestCase
$metadataDriver = $this->createMetadataDriver($type, __DIR__ . '/export/' . $type);
$em = $this->createEntityManager($metadataDriver);
$cmf = $this->createClassMetadataFactory($em, $type);
$metadata = $cmf->getAllMetadata();
$metadatas = $cmf->getAllMetadata();
self::assertCount(1, $metadata);
foreach ($metadatas as $metadata) {
if ($metadata->name === ExportedUser::class) {
return $metadata;
}
}
$class = current($metadata);
self::assertEquals(ExportedUser::class, $class->name);
return $class;
$this->fail('Expected metadata not found');
}
/** @depends testExportedMetadataCanBeReadBackIn */
@@ -388,12 +392,11 @@ abstract class ClassMetadataExporterTestCase extends OrmTestCase
}
}
class Address
{
}
/** @Entity */
class Phonenumber
{
}
/** @Entity */
class Group
{
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Tools\Export;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
/**
* @Entity
*/
class Address
{
/**
* @var int
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
}

View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Tools\Export\Address"/>
</doctrine-mapping>

View File

@@ -0,0 +1,2 @@
Doctrine\Tests\ORM\Tools\Export\Address:
type: entity