Merge pull request #1433 from goetas/slc-check-to-classmetadatainfo
Check for non-cacheable entities on metadata level, not at runtime
This commit is contained in:
commit
506df640b5
@ -267,10 +267,6 @@ class DefaultQueryCache implements QueryCache
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! isset($assoc['cache'])) {
|
|
||||||
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||||
$assocRegion = $assocPersister->getCacheRegion();
|
$assocRegion = $assocPersister->getCacheRegion();
|
||||||
$assocMetadata = $assocPersister->getClassMetadata();
|
$assocMetadata = $assocPersister->getClassMetadata();
|
||||||
|
@ -234,14 +234,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
|||||||
$class = $this->metadataFactory->getMetadataFor($className);
|
$class = $this->metadataFactory->getMetadataFor($className);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($class->containsForeignIdentifier) {
|
|
||||||
foreach ($class->associationMappings as $name => $assoc) {
|
|
||||||
if (!empty($assoc['id']) && !isset($assoc['cache'])) {
|
|
||||||
throw CacheException::nonCacheableEntityAssociation($class->name, $name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
|
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
|
||||||
$cached = $this->region->put($key, $entry);
|
$cached = $this->region->put($key, $entry);
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
|||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||||
use Doctrine\Common\ClassLoader;
|
use Doctrine\Common\ClassLoader;
|
||||||
|
use Doctrine\ORM\Cache\CacheException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
|
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
|
||||||
@ -1078,6 +1079,17 @@ class ClassMetadataInfo implements ClassMetadata
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function enableAssociationCache($fieldName, array $cache)
|
public function enableAssociationCache($fieldName, array $cache)
|
||||||
|
{
|
||||||
|
$this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults ($fieldName, $cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param array $cache
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAssociationCacheDefaults($fieldName, array $cache)
|
||||||
{
|
{
|
||||||
if ( ! isset($cache['usage'])) {
|
if ( ! isset($cache['usage'])) {
|
||||||
$cache['usage'] = isset($this->cache['usage'])
|
$cache['usage'] = isset($this->cache['usage'])
|
||||||
@ -1089,7 +1101,7 @@ class ClassMetadataInfo implements ClassMetadata
|
|||||||
$cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
|
$cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->associationMappings[$fieldName]['cache'] = $cache;
|
return $cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1475,6 +1487,10 @@ class ClassMetadataInfo implements ClassMetadata
|
|||||||
if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
|
if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
|
||||||
$this->isIdentifierComposite = true;
|
$this->isIdentifierComposite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->cache && !isset($mapping['cache'])) {
|
||||||
|
throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mandatory attributes for both sides
|
// Mandatory attributes for both sides
|
||||||
|
@ -273,7 +273,13 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
|||||||
|
|
||||||
$mapping = array();
|
$mapping = array();
|
||||||
$mapping['fieldName'] = $property->getName();
|
$mapping['fieldName'] = $property->getName();
|
||||||
|
// Evaluate @Cache annotation
|
||||||
|
if (($cacheAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Cache')) !== null) {
|
||||||
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], array(
|
||||||
|
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
|
||||||
|
'region' => $cacheAnnot->region,
|
||||||
|
));
|
||||||
|
}
|
||||||
// Check for JoinColumn/JoinColumns annotations
|
// Check for JoinColumn/JoinColumns annotations
|
||||||
$joinColumns = array();
|
$joinColumns = array();
|
||||||
|
|
||||||
@ -396,14 +402,6 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
|||||||
$mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
|
$mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
|
||||||
$metadata->mapEmbedded($mapping);
|
$metadata->mapEmbedded($mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate @Cache annotation
|
|
||||||
if (($cacheAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Cache')) !== null) {
|
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], array(
|
|
||||||
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
|
|
||||||
'region' => $cacheAnnot->region,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate AssociationOverrides annotation
|
// Evaluate AssociationOverrides annotation
|
||||||
|
@ -384,12 +384,12 @@ class XmlDriver extends FileDriver
|
|||||||
$mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']);
|
$mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapOneToOne($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($oneToOneElement->cache)) {
|
if (isset($oneToOneElement->cache)) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapOneToOne($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,12 +428,12 @@ class XmlDriver extends FileDriver
|
|||||||
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapOneToMany($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($oneToManyElement->cache)) {
|
if (isset($oneToManyElement->cache)) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapOneToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,12 +473,13 @@ class XmlDriver extends FileDriver
|
|||||||
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
|
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapManyToOne($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($manyToOneElement->cache)) {
|
if (isset($manyToOneElement->cache)) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapManyToOne($mapping);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,12 +544,12 @@ class XmlDriver extends FileDriver
|
|||||||
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapManyToMany($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($manyToManyElement->cache)) {
|
if (isset($manyToManyElement->cache)) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapManyToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,12 +388,12 @@ class YamlDriver extends FileDriver
|
|||||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval'];
|
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapOneToOne($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($oneToOneElement['cache'])) {
|
if (isset($oneToOneElement['cache'])) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache']));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapOneToOne($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,12 +426,13 @@ class YamlDriver extends FileDriver
|
|||||||
$mapping['indexBy'] = $oneToManyElement['indexBy'];
|
$mapping['indexBy'] = $oneToManyElement['indexBy'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapOneToMany($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($oneToManyElement['cache'])) {
|
if (isset($oneToManyElement['cache'])) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache']));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapOneToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,12 +476,12 @@ class YamlDriver extends FileDriver
|
|||||||
$mapping['cascade'] = $manyToOneElement['cascade'];
|
$mapping['cascade'] = $manyToOneElement['cascade'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapManyToOne($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($manyToOneElement['cache'])) {
|
if (isset($manyToOneElement['cache'])) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache']));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapManyToOne($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,12 +553,12 @@ class YamlDriver extends FileDriver
|
|||||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
|
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadata->mapManyToMany($mapping);
|
|
||||||
|
|
||||||
// Evaluate second level cache
|
// Evaluate second level cache
|
||||||
if (isset($manyToManyElement['cache'])) {
|
if (isset($manyToManyElement['cache'])) {
|
||||||
$metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache']));
|
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$metadata->mapManyToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,30 +482,6 @@ class DefaultQueryCacheTest extends OrmTestCase
|
|||||||
$this->assertNull($this->queryCache->get($key, $rsm));
|
$this->assertNull($this->queryCache->get($key, $rsm));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException Doctrine\ORM\Cache\CacheException
|
|
||||||
* @expectedExceptionMessage Entity association field "Doctrine\Tests\Models\Cache\City#travels" not configured as part of the second-level cache.
|
|
||||||
*/
|
|
||||||
public function testQueryNotCacheableAssociationException()
|
|
||||||
{
|
|
||||||
$uow = $this->em->getUnitOfWork();
|
|
||||||
$key = new QueryCacheKey('query.key1', 0);
|
|
||||||
$rsm = new ResultSetMappingBuilder($this->em);
|
|
||||||
$cityClass = $this->em->getClassMetadata(City::CLASSNAME);
|
|
||||||
$city = new City("City 1", null);
|
|
||||||
$result = array(
|
|
||||||
$city
|
|
||||||
);
|
|
||||||
|
|
||||||
$cityClass->setFieldValue($city, 'id', 1);
|
|
||||||
|
|
||||||
$rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c');
|
|
||||||
$rsm->addJoinedEntityFromClassMetadata(Travel::CLASSNAME, 't', 'c', 'travels', array('id' => 't_id'));
|
|
||||||
$uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => null));
|
|
||||||
|
|
||||||
$this->queryCache->put($key, $rsm, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException Doctrine\ORM\Cache\CacheException
|
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||||
* @expectedExceptionMessage Second level cache does not support scalar results.
|
* @expectedExceptionMessage Second level cache does not support scalar results.
|
||||||
|
@ -23,6 +23,19 @@ class AnnotationDriverTest extends AbstractMappingDriverTest
|
|||||||
$annotationDriver->loadMetadataForClass('stdClass', $cm);
|
$annotationDriver->loadMetadataForClass('stdClass', $cm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||||
|
* @expectedExceptionMessage Entity association field "Doctrine\Tests\ORM\Mapping\AnnotationSLC#foo" not configured as part of the second-level cache.
|
||||||
|
*/
|
||||||
|
public function testFailingSecondLevelCacheAssociation()
|
||||||
|
{
|
||||||
|
$className = 'Doctrine\Tests\ORM\Mapping\AnnotationSLC';
|
||||||
|
$mappingDriver = $this->_loadDriver();
|
||||||
|
|
||||||
|
$class = new ClassMetadata($className);
|
||||||
|
$mappingDriver->loadMetadataForClass($className, $class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group DDC-268
|
* @group DDC-268
|
||||||
*/
|
*/
|
||||||
@ -351,3 +364,26 @@ class InvalidFetchOption
|
|||||||
*/
|
*/
|
||||||
private $collection;
|
private $collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @Cache
|
||||||
|
*/
|
||||||
|
class AnnotationSLC
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @ManyToOne(targetEntity="AnnotationSLCFoo")
|
||||||
|
*/
|
||||||
|
public $foo;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class AnnotationSLCFoo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Column(type="string")
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
}
|
||||||
|
@ -33,4 +33,17 @@ class PHPMappingDriverTest extends AbstractMappingDriverTest
|
|||||||
{
|
{
|
||||||
$this->createClassMetadata('Doctrine\Tests\Models\DDC889\DDC889Class');
|
$this->createClassMetadata('Doctrine\Tests\Models\DDC889\DDC889Class');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||||
|
* @expectedExceptionMessage Entity association field "Doctrine\Tests\ORM\Mapping\PHPSLC#foo" not configured as part of the second-level cache.
|
||||||
|
*/
|
||||||
|
public function testFailingSecondLevelCacheAssociation()
|
||||||
|
{
|
||||||
|
$className = 'Doctrine\Tests\ORM\Mapping\PHPSLC';
|
||||||
|
$mappingDriver = $this->_loadDriver();
|
||||||
|
|
||||||
|
$class = new ClassMetadata($className);
|
||||||
|
$mappingDriver->loadMetadataForClass($className, $class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,19 @@ class XmlMappingDriverTest extends AbstractMappingDriverTest
|
|||||||
$this->assertEquals($expectedMap, $class->discriminatorMap);
|
$this->assertEquals($expectedMap, $class->discriminatorMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Doctrine\ORM\Cache\CacheException
|
||||||
|
* @expectedExceptionMessage Entity association field "Doctrine\Tests\ORM\Mapping\XMLSLC#foo" not configured as part of the second-level cache.
|
||||||
|
*/
|
||||||
|
public function testFailingSecondLevelCacheAssociation()
|
||||||
|
{
|
||||||
|
$className = 'Doctrine\Tests\ORM\Mapping\XMLSLC';
|
||||||
|
$mappingDriver = $this->_loadDriver();
|
||||||
|
|
||||||
|
$class = new ClassMetadata($className);
|
||||||
|
$mappingDriver->loadMetadataForClass($className, $class);
|
||||||
|
}
|
||||||
|
|
||||||
public function testIdentifierWithAssociationKey()
|
public function testIdentifierWithAssociationKey()
|
||||||
{
|
{
|
||||||
$driver = $this->_loadDriver();
|
$driver = $this->_loadDriver();
|
||||||
@ -176,3 +189,12 @@ class CTI
|
|||||||
class CTIFoo extends CTI {}
|
class CTIFoo extends CTI {}
|
||||||
class CTIBar extends CTI {}
|
class CTIBar extends CTI {}
|
||||||
class CTIBaz extends CTI {}
|
class CTIBaz extends CTI {}
|
||||||
|
|
||||||
|
class XMLSLC
|
||||||
|
{
|
||||||
|
public $foo;
|
||||||
|
}
|
||||||
|
class XMLSLCFoo
|
||||||
|
{
|
||||||
|
public $id;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
|
|
||||||
|
$metadata->enableCache(array(
|
||||||
|
'usage' => ClassMetadataInfo::CACHE_USAGE_READ_ONLY
|
||||||
|
));
|
||||||
|
$metadata->mapManyToOne(array(
|
||||||
|
'fieldName' => 'foo',
|
||||||
|
'id' => true,
|
||||||
|
'targetEntity' => 'PHPSLCFoo'
|
||||||
|
));
|
@ -0,0 +1,13 @@
|
|||||||
|
<?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
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
<entity name="Doctrine\Tests\ORM\Mapping\XMLSLC">
|
||||||
|
<cache usage="NONSTRICT_READ_WRITE" />
|
||||||
|
<id name="foo" association-key="true"/>
|
||||||
|
<many-to-one field="foo" target-entity="Doctrine\Tests\ORM\Mapping\XMLSLCFoo">
|
||||||
|
<join-column name="foo_id" referenced-column-name="id" />
|
||||||
|
</many-to-one>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
Loading…
x
Reference in New Issue
Block a user