1
0
mirror of synced 2024-12-05 03:06:05 +03:00

Merge branch 'hotfix/#1113-composite-pk-improved-support'

Close #1113
This commit is contained in:
Marco Pivetta 2015-02-16 01:17:48 +00:00
commit f90897465c
18 changed files with 664 additions and 156 deletions

View File

@ -89,7 +89,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
/* @var $entityEntries \Doctrine\ORM\Cache\EntityCacheEntry[] */
foreach ($entityEntries as $index => $entityEntry) {
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints);
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
}
array_walk($list, function($entity, $index) use ($collection) {

View File

@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
@ -233,6 +234,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$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);
$cached = $this->region->put($key, $entry);

View File

@ -317,4 +317,12 @@ class ORMException extends Exception
{
return new self("It is not allowed to overwrite internal function '$functionName' in the DQL parser through user-defined functions.");
}
/**
* @return ORMException
*/
public static function cantUseInOperatorOnCompositeKeys()
{
return new self("Can't use IN operator on entities that have composite keys.");
}
}

View File

@ -281,8 +281,11 @@ class BasicEntityPersister implements EntityPersister
$stmt->execute();
if ($isPostInsertId) {
$id = $idGenerator->generate($this->em, $entity);
$postInsertIds[$id] = $entity;
$generatedId = $idGenerator->generate($this->em, $entity);
$id = array(
$this->class->identifier[0] => $generatedId
);
$postInsertIds[$generatedId] = $entity;
} else {
$id = $this->class->getIdentifierValues($entity);
}
@ -304,11 +307,11 @@ class BasicEntityPersister implements EntityPersister
* entities version field.
*
* @param object $entity
* @param mixed $id
* @param array $id
*
* @return void
*/
protected function assignDefaultVersionValue($entity, $id)
protected function assignDefaultVersionValue($entity, array $id)
{
$value = $this->fetchVersionValue($this->class, $id);
@ -319,11 +322,11 @@ class BasicEntityPersister implements EntityPersister
* Fetches the current version value of a versioned entity.
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $versionedClass
* @param mixed $id
* @param array $id
*
* @return mixed
*/
protected function fetchVersionValue($versionedClass, $id)
protected function fetchVersionValue($versionedClass, array $id)
{
$versionField = $versionedClass->versionField;
$tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
@ -335,7 +338,7 @@ class BasicEntityPersister implements EntityPersister
. ' FROM ' . $tableName
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, (array) $id);
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);
$value = $this->conn->fetchColumn($sql, array_values($flatId));
@ -852,12 +855,12 @@ class BasicEntityPersister implements EntityPersister
list($params, $types) = $valueVisitor->getParamsAndTypes();
foreach ($params as $param) {
$sqlParams[] = PersisterHelper::getIdentifierValues($param, $this->em);
$sqlParams = array_merge($sqlParams, $this->getValues($param));
}
foreach ($types as $type) {
list($field, $value) = $type;
$sqlTypes[] = $this->getType($field, $value, $this->class);
list ($field, $value) = $type;
$sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
}
return array($sqlParams, $sqlTypes);
@ -1565,40 +1568,61 @@ class BasicEntityPersister implements EntityPersister
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
$placeholder = '?';
$condition = $this->getSelectConditionStatementColumnSQL($field, $assoc);
$selectedColumns = array();
$columns = $this->getSelectConditionStatementColumnSQL($field, $assoc);
if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) {
$placeholder = Type::getType($this->class->getTypeOfField($field))->convertToDatabaseValueSQL($placeholder, $this->platform);
if (count($columns) > 1 && $comparison === Comparison::IN) {
/*
* @todo try to support multi-column IN expressions.
* Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B'))
*/
throw ORMException::cantUseInOperatorOnCompositeKeys();
}
if ($comparison !== null) {
foreach ($columns as $column) {
$placeholder = '?';
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
return $condition . ' IS NULL';
} else if ($comparison === Comparison::NEQ && $value === null) {
return $condition . ' IS NOT NULL';
if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) {
$placeholder = Type::getType($this->class->getTypeOfField($field))->convertToDatabaseValueSQL($placeholder, $this->platform);
}
return $condition . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
}
if (null !== $comparison) {
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && null ===$value) {
$selectedColumns[] = $column . ' IS NULL';
continue;
}
if (is_array($value)) {
$in = sprintf('%s IN (%s)' , $condition, $placeholder);
if ($comparison === Comparison::NEQ && null === $value) {
$selectedColumns[] = $column . ' IS NOT NULL';
continue;
}
if (false !== array_search(null, $value, true)) {
return sprintf('(%s OR %s IS NULL)' , $in, $condition);
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
continue;
}
return $in;
if (is_array($value)) {
$in = sprintf('%s IN (%s)', $column, $placeholder);
if (false !== array_search(null, $value, true)) {
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
continue;
}
$selectedColumns[] = $in;
continue;
}
if (null === $value) {
$selectedColumns[] = sprintf('%s IS NULL', $column);
continue;
}
$selectedColumns[] = sprintf('%s = %s', $column, $placeholder);
}
if ($value === null) {
return sprintf('%s IS NULL' , $condition);
}
return sprintf('%s = %s' , $condition, $placeholder);
return implode(' AND ', $selectedColumns);
}
/**
@ -1607,7 +1631,7 @@ class BasicEntityPersister implements EntityPersister
* @param string $field
* @param array|null $assoc
*
* @return string
* @return string[]
*
* @throws \Doctrine\ORM\ORMException
*/
@ -1618,36 +1642,45 @@ class BasicEntityPersister implements EntityPersister
? $this->class->fieldMappings[$field]['inherited']
: $this->class->name;
return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform);
return array($this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform));
}
if (isset($this->class->associationMappings[$field])) {
$association = $this->class->associationMappings[$field];
// Many-To-Many requires join table check for joinColumn
$columns = array();
$class = $this->class;
if ($association['type'] === ClassMetadata::MANY_TO_MANY) {
if ( ! $association['isOwningSide']) {
$association = $assoc;
}
$joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform);
$joinColumn = $assoc['isOwningSide']
? $association['joinTable']['joinColumns'][0]
: $association['joinTable']['inverseJoinColumns'][0];
$joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform);
$joinColumns = $assoc['isOwningSide']
? $association['joinTable']['joinColumns']
: $association['joinTable']['inverseJoinColumns'];
return $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
foreach ($joinColumns as $joinColumn) {
$columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
}
} else {
if ( ! $association['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->class->name, $field);
}
$className = (isset($association['inherited']))
? $association['inherited']
: $this->class->name;
foreach ($association['joinColumns'] as $joinColumn) {
$columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
}
}
if ( ! $association['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->class->name, $field);
}
$joinColumn = $association['joinColumns'][0];
$className = (isset($association['inherited']))
? $association['inherited']
: $this->class->name;
return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
return $columns;
}
if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) {
@ -1655,7 +1688,7 @@ class BasicEntityPersister implements EntityPersister
// therefore checking for spaces and function calls which are not allowed.
// found a join column condition, not really a "field"
return $field;
return array($field);
}
throw ORMException::unrecognizedField($field);
@ -1778,8 +1811,8 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types[] = $this->getType($field, $value, $this->class);
$params[] = $this->getValue($value);
$types = array_merge($types, $this->getTypes($field, $value, $this->class));
$params = array_merge($params, $this->getValues($value));
}
return array($params, $types);
@ -1807,56 +1840,100 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types[] = $this->getType($criterion['field'], $criterion['value'], $criterion['class']);
$params[] = PersisterHelper::getIdentifierValues($criterion['value'], $this->em);
$types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class']));
$params = array_merge($params, $this->getValues($criterion['value']));
}
return array($params, $types);
}
/**
* Infers field type to be used by parameter type casting.
* Infers field types to be used by parameter type casting.
*
* @param string $fieldName
* @param mixed $value
* @param ClassMetadata $class
* @param string $field
* @param mixed $value
*
* @return integer
* @return array
*
* @throws \Doctrine\ORM\Query\QueryException
*/
private function getType($fieldName, $value, ClassMetadata $class)
private function getTypes($field, $value, ClassMetadata $class)
{
$type = PersisterHelper::getTypeOfField($fieldName, $class, $this->em);
$types = array();
if (is_array($value)) {
$type = Type::getType($type)->getBindingType();
$type += Connection::ARRAY_PARAM_OFFSET;
switch (true) {
case (isset($class->fieldMappings[$field])):
$types = array_merge($types, PersisterHelper::getTypeOfField($field, $class, $this->em));
break;
case (isset($class->associationMappings[$field])):
$assoc = $class->associationMappings[$field];
$class = $this->em->getClassMetadata($assoc['targetEntity']);
if (! $assoc['isOwningSide']) {
$assoc = $class->associationMappings[$assoc['mappedBy']];
$class = $this->em->getClassMetadata($assoc['targetEntity']);
}
$columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY
? $assoc['relationToTargetKeyColumns']
: $assoc['sourceToTargetKeyColumns'];
foreach ($columns as $column){
$types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em);
}
break;
default:
$types[] = null;
break;
}
return $type;
if (is_array($value)) {
return array_map(
function ($type) {
return Type::getType($type)->getBindingType() + Connection::ARRAY_PARAM_OFFSET;
},
$types
);
}
return $types;
}
/**
* Retrieves parameter value.
* Retrieves the parameters that identifies a value.
*
* @param mixed $value
*
* @return mixed
* @return array
*/
private function getValue($value)
private function getValues($value)
{
if ( ! is_array($value)) {
return $this->getIndividualValue($value);
if (is_array($value)) {
$newValue = array();
foreach ($value as $itemValue) {
$newValue = array_merge($newValue, $this->getValues($itemValue));
}
return array($newValue);
}
$newValue = array();
if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$class = $this->em->getClassMetadata(get_class($value));
if ($class->isIdentifierComposite) {
$newValue = array();
foreach ($value as $itemValue) {
$newValue[] = $this->getIndividualValue($itemValue);
foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, $this->getValues($innerValue));
}
return $newValue;
}
}
return $newValue;
return array($this->getIndividualValue($value));
}
/**

View File

@ -176,8 +176,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$rootTableStmt->execute();
if ($isPostInsertId) {
$id = $idGenerator->generate($this->em, $entity);
$postInsertIds[$id] = $entity;
$generatedId = $idGenerator->generate($this->em, $entity);
$id = array(
$this->class->identifier[0] => $generatedId
);
$postInsertIds[$generatedId] = $entity;
} else {
$id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
}
@ -572,7 +575,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/**
* {@inheritdoc}
*/
protected function assignDefaultVersionValue($entity, $id)
protected function assignDefaultVersionValue($entity, array $id)
{
$value = $this->fetchVersionValue($this->getVersionedClassMetadata(), $id);
$this->class->setFieldValue($entity, $this->class->versionField, $value);

View File

@ -49,6 +49,7 @@ use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
use Doctrine\ORM\Utility\IdentifierFlattener;
use Doctrine\ORM\Cache\AssociationCacheEntry;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
@ -2490,22 +2491,7 @@ class UnitOfWork implements PropertyChangedListener
$class = $this->em->getClassMetadata($className);
//$isReadOnly = isset($hints[Query::HINT_READ_ONLY]);
if ($class->isIdentifierComposite) {
$id = array();
foreach ($class->identifier as $fieldName) {
$id[$fieldName] = isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName];
}
} else {
$id = isset($class->associationMappings[$class->identifier[0]])
? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]
: $data[$class->identifier[0]];
$id = array($class->identifier[0] => $id);
}
$id = $this->identifierFlattener->flattenIdentifier($class, $data);
$idHash = implode(' ', $id);
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
@ -2643,6 +2629,12 @@ class UnitOfWork implements PropertyChangedListener
} else {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
} elseif ($targetClass->containsForeignIdentifier
&& in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true)
) {
// the missing key is part of target's entity primary key
$associatedId = array();
break;
}
}

View File

@ -71,20 +71,30 @@ final class IdentifierFlattener
{
$flatId = array();
foreach ($id as $idField => $idValue) {
if (isset($class->associationMappings[$idField]) && is_object($idValue)) {
foreach ($class->identifier as $field) {
if (isset($class->associationMappings[$field]) && isset($id[$field]) && is_object($id[$field])) {
/* @var $targetClassMetadata ClassMetadata */
$targetClassMetadata = $this->metadataFactory->getMetadataFor(
$class->associationMappings[$idField]['targetEntity']
$class->associationMappings[$field]['targetEntity']
);
$associatedId = $this->unitOfWork->isInIdentityMap($idValue)
? $this->unitOfWork->getEntityIdentifier($idValue)
: $targetClassMetadata->getIdentifierValues($idValue);
if ($this->unitOfWork->isInIdentityMap($id[$field])) {
$associatedId = $this->flattenIdentifier($targetClassMetadata, $this->unitOfWork->getEntityIdentifier($id[$field]));
} else {
$associatedId = $this->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($id[$field]));
}
$flatId[$idField] = $associatedId[$targetClassMetadata->identifier[0]];
$flatId[$field] = implode(' ', $associatedId);
} elseif (isset($class->associationMappings[$field])) {
$associatedId = array();
foreach ($class->associationMappings[$field]['joinColumns'] as $joinColumn) {
$associatedId[] = $id[$joinColumn['name']];
}
$flatId[$field] = implode(' ', $associatedId);
} else {
$flatId[$idField] = $idValue;
$flatId[$field] = $id[$field];
}
}

View File

@ -40,18 +40,18 @@ class PersisterHelper
* @param ClassMetadata $class
* @param EntityManagerInterface $em
*
* @return string|null
* @return array
*
* @throws QueryException
*/
public static function getTypeOfField($fieldName, ClassMetadata $class, EntityManagerInterface $em)
{
if (isset($class->fieldMappings[$fieldName])) {
return $class->fieldMappings[$fieldName]['type'];
return array($class->fieldMappings[$fieldName]['type']);
}
if ( ! isset($class->associationMappings[$fieldName])) {
return null;
return array();
}
$assoc = $class->associationMappings[$fieldName];
@ -60,20 +60,20 @@ class PersisterHelper
return self::getTypeOfField($assoc['mappedBy'], $em->getClassMetadata($assoc['targetEntity']), $em);
}
if (($assoc['type'] & ClassMetadata::MANY_TO_MANY) > 0) {
if ($assoc['type'] & ClassMetadata::MANY_TO_MANY) {
$joinData = $assoc['joinTable'];
} else {
$joinData = $assoc;
}
if (count($joinData['joinColumns']) > 1) {
throw QueryException::associationPathCompositeKeyNotSupported();
$types = array();
$targetClass = $em->getClassMetadata($assoc['targetEntity']);
foreach ($joinData['joinColumns'] as $joinColumn) {
$types[] = self::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $em);
}
$targetColumnName = $joinData['joinColumns'][0]['referencedColumnName'];
$targetClass = $em->getClassMetadata($assoc['targetEntity']);
return self::getTypeOfColumn($targetColumnName, $targetClass, $em);
return $types;
}
/**
@ -133,40 +133,4 @@ class PersisterHelper
$class->getName()
));
}
/**
* @param mixed $value
* @param EntityManagerInterface $em
*
* @return mixed
*/
public static function getIdentifierValues($value, EntityManagerInterface $em)
{
if ( ! is_array($value)) {
return self::getIndividualValue($value, $em);
}
$newValue = array();
foreach ($value as $fieldName => $fieldValue) {
$newValue[$fieldName] = self::getIndividualValue($fieldValue, $em);
}
return $newValue;
}
/**
* @param mixed $value
* @param EntityManagerInterface $em
*
* @return mixed
*/
private static function getIndividualValue($value, EntityManagerInterface $em)
{
if ( ! is_object($value) || ! $em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
return $value;
}
return $em->getUnitOfWork()->getSingleIdentifierValue($value);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Doctrine\Tests\Models\GeoNames;
/**
* @Entity
* @Table(name="geonames_admin1")
* @Cache
*/
class Admin1
{
/**
* @Id
* @Column(type="integer", length=25)
* @GeneratedValue(strategy="NONE")
*/
public $id;
/**
* @Id
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country", referencedColumnName="id")
* @Cache
*/
public $country;
/**
* @OneToMany(targetEntity="Admin1AlternateName", mappedBy="admin1")
* @Cache
*/
public $names = array();
/**
* @Column(type="string", length=255);
*/
public $name;
public function __construct($id, $name, Country $country)
{
$this->id = $id;
$this->name = $name;
$this->country = $country;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Doctrine\Tests\Models\GeoNames;
/**
* @Entity
* @Table(name="geonames_admin1_alternate_name")
* @Cache
*/
class Admin1AlternateName
{
/**
* @Id
* @Column(type="string", length=25)
* @GeneratedValue(strategy="NONE")
*/
public $id;
/**
* @ManyToOne(targetEntity="Admin1", inversedBy="names")
* @JoinColumns({
* @JoinColumn(name="admin1", referencedColumnName="id"),
* @JoinColumn(name="country", referencedColumnName="country")
* })
* @Cache
*/
public $admin1;
/**
* @Column(type="string", length=255);
*/
public $name;
public function __construct($id, $name, Admin1 $admin1)
{
$this->id = $id;
$this->name = $name;
$this->admin1 = $admin1;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Doctrine\Tests\Models\GeoNames;
/**
* @Entity
* @Table(name="geonames_city")
* @Cache
*/
class City
{
/**
* @Id
* @Column(type="string", length=25)
* @GeneratedValue(strategy="NONE")
*/
public $id;
/**
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country", referencedColumnName="id")
* @Cache
*/
public $country;
/**
* @ManyToOne(targetEntity="Admin1")
* @JoinColumns({
* @JoinColumn(name="admin1", referencedColumnName="id"),
* @JoinColumn(name="country", referencedColumnName="country")
* })
* @Cache
*/
public $admin1;
/**
* @Column(type="string", length=255);
*/
public $name;
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Doctrine\Tests\Models\GeoNames;
/**
* @Entity
* @Table(name="geonames_country")
* @Cache
*/
class Country
{
/**
* @Id
* @Column(type="string", length=2)
* @GeneratedValue(strategy="NONE")
*/
public $id;
/**
* @Column(type="string", length=255);
*/
public $name;
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\GeoNames\Country;
use Doctrine\Tests\Models\GeoNames\Admin1;
use Doctrine\Tests\Models\GeoNames\Admin1AlternateName;
class CompositePrimaryKeyWithAssociationsTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
$this->useModelSet('geonames');
parent::setUp();
$it = new Country("IT", "Italy");
$this->_em->persist($it);
$this->_em->flush();
$admin1 = new Admin1(1, "Rome", $it);
$this->_em->persist($admin1);
$this->_em->flush();
$name1 = new Admin1AlternateName(1, "Roma", $admin1);
$name2 = new Admin1AlternateName(2, "Rome", $admin1);
$admin1->names[] = $name1;
$admin1->names[] = $name2;
$this->_em->persist($admin1);
$this->_em->persist($name1);
$this->_em->persist($name2);
$this->_em->flush();
$this->_em->clear();
}
public function testFindByAbleToGetCompositeEntitiesWithMixedTypeIdentifiers()
{
$admin1Repo = $this->_em->getRepository('Doctrine\Tests\Models\GeoNames\Admin1');
$admin1NamesRepo = $this->_em->getRepository('Doctrine\Tests\Models\GeoNames\Admin1AlternateName');
$admin1Rome = $admin1Repo->findOneBy(array('country' => 'IT', 'id' => 1));
$names = $admin1NamesRepo->findBy(array('admin1' => $admin1Rome));
$this->assertCount(2, $names);
$name1 = $admin1NamesRepo->findOneBy(array('admin1' => $admin1Rome, 'id' => 1));
$name2 = $admin1NamesRepo->findOneBy(array('admin1' => $admin1Rome, 'id' => 2));
$this->assertEquals(1, $name1->id);
$this->assertEquals("Roma", $name1->name);
$this->assertEquals(2, $name2->id);
$this->assertEquals("Rome", $name2->name);
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\Models\GeoNames\Country;
use Doctrine\Tests\Models\GeoNames\Admin1;
use Doctrine\Tests\Models\GeoNames\Admin1AlternateName;
class SecondLevelCacheCompositePrimaryKeyWithAssociationsTest extends OrmFunctionalTestCase
{
/**
* @var \Doctrine\ORM\Cache
*/
protected $cache;
public function setUp()
{
$this->enableSecondLevelCache();
$this->useModelSet('geonames');
parent::setUp();
$this->cache = $this->_em->getCache();
$it = new Country("IT", "Italy");
$this->_em->persist($it);
$this->_em->flush();
$admin1 = new Admin1(1, "Rome", $it);
$this->_em->persist($admin1);
$this->_em->flush();
$name1 = new Admin1AlternateName(1, "Roma", $admin1);
$name2 = new Admin1AlternateName(2, "Rome", $admin1);
$admin1->names[] = $name1;
$admin1->names[] = $name2;
$this->_em->persist($admin1);
$this->_em->persist($name1);
$this->_em->persist($name2);
$this->_em->flush();
$this->_em->clear();
$this->evictRegions();
}
public function testFindByReturnsCachedEntity()
{
$admin1Repo = $this->_em->getRepository('Doctrine\Tests\Models\GeoNames\Admin1');
$queries = $this->getCurrentQueryCount();
$admin1Rome = $admin1Repo->findOneBy(array('country' => 'IT', 'id' => 1));
$this->assertEquals("Italy", $admin1Rome->country->name);
$this->assertEquals(2, count($admin1Rome->names));
$this->assertEquals($queries + 3, $this->getCurrentQueryCount());
$this->_em->clear();
$queries = $this->getCurrentQueryCount();
$admin1Rome = $admin1Repo->findOneBy(array('country' => 'IT', 'id' => 1));
$this->assertEquals("Italy", $admin1Rome->country->name);
$this->assertEquals(2, count($admin1Rome->names));
$this->assertEquals($queries, $this->getCurrentQueryCount());
}
private function evictRegions()
{
$this->cache->evictQueryRegions();
$this->cache->evictEntityRegions();
$this->cache->evictCollectionRegions();
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Doctrine\Tests\ORM\Persisters;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
use Doctrine\Tests\Models\GeoNames\Admin1;
use Doctrine\Tests\Models\GeoNames\Country;
class BasicEntityPersisterCompositeTypeParametersTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var BasicEntityPersister
*/
protected $_persister;
/**
* @var \Doctrine\ORM\EntityManager
*/
protected $_em;
/**
* {@inheritDoc}
*/
protected function setUp()
{
parent::setUp();
$this->_em = $this->_getTestEntityManager();
$this->_em->getClassMetadata('Doctrine\Tests\Models\GeoNames\Country');
$this->_em->getClassMetadata('Doctrine\Tests\Models\GeoNames\Admin1');
$this->_em->getClassMetadata('Doctrine\Tests\Models\GeoNames\Admin1AlternateName');
$this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata('Doctrine\Tests\Models\GeoNames\Admin1AlternateName'));
}
public function testExpandParametersWillExpandCompositeEntityKeys()
{
$country = new Country("IT", "Italy");
$admin1 = new Admin1(10, "Rome", $country);
list ($values, $types) = $this->_persister->expandParameters(array(
'admin1' => $admin1
));
$this->assertEquals(array('integer', 'string'), $types);
$this->assertEquals(array(10, 'IT'), $values);
}
public function testExpandCriteriaParametersWillExpandCompositeEntityKeys()
{
$country = new Country("IT", "Italy");
$admin1 = new Admin1(10, "Rome", $country);
$criteria = Criteria::create();
$criteria->andWhere(Criteria::expr()->eq("admin1", $admin1));
list ($values, $types) = $this->_persister->expandCriteriaParameters($criteria);
$this->assertEquals(array('integer', 'string'), $types);
$this->assertEquals(array(10, 'IT'), $values);
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Doctrine\Tests\ORM\Persisters;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
use Doctrine\Tests\Models\CustomType\CustomTypeParent;
use Doctrine\Tests\Models\CustomType\CustomTypeChild;
use Doctrine\Common\Collections\Expr\Comparison;
class BasicEntityPersisterCompositeTypeSqlTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var BasicEntityPersister
*/
protected $_persister;
/**
* @var \Doctrine\ORM\EntityManager
*/
protected $_em;
/**
* {@inheritDoc}
*/
protected function setUp()
{
parent::setUp();
$this->_em = $this->_getTestEntityManager();
$this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata('Doctrine\Tests\Models\GeoNames\Admin1AlternateName'));
}
public function testSelectConditionStatementEq()
{
$statement = $this->_persister->getSelectConditionStatementSQL('admin1', 1, array(), Comparison::EQ);
$this->assertEquals('t0.admin1 = ? AND t0.country = ?', $statement);
}
public function testSelectConditionStatementEqNull()
{
$statement = $this->_persister->getSelectConditionStatementSQL('admin1', null, array(), Comparison::IS);
$this->assertEquals('t0.admin1 IS NULL AND t0.country IS NULL', $statement);
}
public function testSelectConditionStatementNeqNull()
{
$statement = $this->_persister->getSelectConditionStatementSQL('admin1', null, array(), Comparison::NEQ);
$this->assertEquals('t0.admin1 IS NOT NULL AND t0.country IS NOT NULL', $statement);
}
/**
* @expectedException Doctrine\ORM\ORMException
*/
public function testSelectConditionStatementIn()
{
$this->_persister->getSelectConditionStatementSQL('admin1', array(), array(), Comparison::IN);
}
}

View File

@ -9,6 +9,7 @@ use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\Models\Cache\Flight;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Utility\IdentifierFlattener;
/**
* Test the IdentifierFlattener utility class
*
@ -87,7 +88,7 @@ class IdentifierFlattenerTest extends OrmFunctionalTestCase
$this->assertArrayHasKey('secondEntity', $flatIds, 'It should be called secondEntity');
$this->assertSame($id['secondEntity']->id, $flatIds['secondEntity']);
$this->assertEquals($id['secondEntity']->id, $flatIds['secondEntity']);
}
/**
@ -115,8 +116,8 @@ class IdentifierFlattenerTest extends OrmFunctionalTestCase
$this->assertArrayHasKey('leavingFrom', $id);
$this->assertArrayHasKey('goingTo', $id);
$this->assertSame($leeds, $id['leavingFrom']);
$this->assertSame($london, $id['goingTo']);
$this->assertEquals($leeds, $id['leavingFrom']);
$this->assertEquals($london, $id['goingTo']);
$flatIds = $this->identifierFlattener->flattenIdentifier($class, $id);
@ -125,7 +126,7 @@ class IdentifierFlattenerTest extends OrmFunctionalTestCase
$this->assertArrayHasKey('leavingFrom', $flatIds);
$this->assertArrayHasKey('goingTo', $flatIds);
$this->assertSame($id['leavingFrom']->getId(), $flatIds['leavingFrom']);
$this->assertSame($id['goingTo']->getId(), $flatIds['goingTo']);
$this->assertEquals($id['leavingFrom']->getId(), $flatIds['leavingFrom']);
$this->assertEquals($id['goingTo']->getId(), $flatIds['goingTo']);
}
}

View File

@ -252,6 +252,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\ValueConversionType\InversedManyToManyExtraLazyEntity',
'Doctrine\Tests\Models\ValueConversionType\OwningManyToManyExtraLazyEntity'
),
'geonames' => array(
'Doctrine\Tests\Models\GeoNames\Country',
'Doctrine\Tests\Models\GeoNames\Admin1',
'Doctrine\Tests\Models\GeoNames\Admin1AlternateName',
'Doctrine\Tests\Models\GeoNames\City'
)
);
/**
@ -483,6 +489,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM vct_owning_manytomany_extralazy');
$conn->executeUpdate('DELETE FROM vct_inversed_manytomany_extralazy');
}
if (isset($this->_usedModelSets['geonames'])) {
$conn->executeUpdate('DELETE FROM geonames_admin1_alternate_name');
$conn->executeUpdate('DELETE FROM geonames_admin1');
$conn->executeUpdate('DELETE FROM geonames_city');
$conn->executeUpdate('DELETE FROM geonames_country');
}
$this->_em->clear();
}
@ -623,7 +635,8 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
}
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array(
realpath(__DIR__ . '/Models/Cache')
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/GeoNames')
), true));
$conn = static::$_sharedConn;