[2.0][DDC-22] Fixed. Also cleaned up association handling. More to come: exception refactorings, proxy simplifications (single proxy class, not two).
This commit is contained in:
parent
e832d1723a
commit
4fc1781d78
@ -100,12 +100,12 @@ class DoctrineException extends \Exception
|
|||||||
if (($message = self::getExceptionMessage($messageKey)) !== false) {
|
if (($message = self::getExceptionMessage($messageKey)) !== false) {
|
||||||
$message = sprintf($message, $arguments);
|
$message = sprintf($message, $arguments);
|
||||||
} else {
|
} else {
|
||||||
$dumper = function ($value) { return var_export($value, true); };
|
//$dumper = function ($value) { return var_export($value, true); };
|
||||||
$message = strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $method));
|
$message = strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $method));
|
||||||
$message = ucfirst(str_replace('_', ' ', $message));
|
$message = ucfirst(str_replace('_', ' ', $message));
|
||||||
if ($arguments) {
|
/*if ($arguments) {
|
||||||
$message .= ' (' . implode(', ', array_map($dumper, $arguments)) . ')';
|
$message .= ' (' . implode(', ', array_map($dumper, $arguments)) . ')';
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return new $class($message, $innerException);
|
return new $class($message, $innerException);
|
||||||
|
5
lib/Doctrine/DBAL/DBALException.php
Normal file
5
lib/Doctrine/DBAL/DBALException.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\DBAL;
|
||||||
|
|
||||||
|
class DBALException extends \Exception {}
|
@ -22,7 +22,7 @@
|
|||||||
namespace Doctrine\ORM\Id;
|
namespace Doctrine\ORM\Id;
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\Common\DoctrineException;
|
use Doctrine\ORM\ORMException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special generator for application-assigned identifiers (doesnt really generate anything).
|
* Special generator for application-assigned identifiers (doesnt really generate anything).
|
||||||
@ -62,7 +62,7 @@ class Assigned extends AbstractIdGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $identifier) {
|
if ( ! $identifier) {
|
||||||
throw DoctrineException::entityMissingAssignedId($entity);
|
throw ORMException::entityMissingAssignedId($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $identifier;
|
return $identifier;
|
||||||
|
@ -78,10 +78,11 @@ abstract class AbstractHydrator
|
|||||||
* @param object $resultSetMapping
|
* @param object $resultSetMapping
|
||||||
* @return IterableResult
|
* @return IterableResult
|
||||||
*/
|
*/
|
||||||
public function iterate($stmt, $resultSetMapping)
|
public function iterate($stmt, $resultSetMapping, array $hints = array())
|
||||||
{
|
{
|
||||||
$this->_stmt = $stmt;
|
$this->_stmt = $stmt;
|
||||||
$this->_rsm = $resultSetMapping;
|
$this->_rsm = $resultSetMapping;
|
||||||
|
$this->_hints = $hints;
|
||||||
$this->_prepare();
|
$this->_prepare();
|
||||||
return new IterableResult($this);
|
return new IterableResult($this);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use Doctrine\DBAL\Connection;
|
|||||||
/**
|
/**
|
||||||
* Hydrator that produces flat, rectangular results of scalar data.
|
* Hydrator that produces flat, rectangular results of scalar data.
|
||||||
* The created result is almost the same as a regular SQL result set, except
|
* The created result is almost the same as a regular SQL result set, except
|
||||||
* that column names are mapped to field names and data type conversions.
|
* that column names are mapped to field names and data type conversions take place.
|
||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
@ -49,10 +49,4 @@ class ScalarHydrator extends AbstractHydrator
|
|||||||
{
|
{
|
||||||
$result[] = $this->_gatherScalarRowData($data, $cache);
|
$result[] = $this->_gatherScalarRowData($data, $cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
protected function _getRowContainer()
|
|
||||||
{
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -170,6 +170,15 @@ final class ClassMetadata extends ClassMetadataInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getColumnValues($entity, array $columns)
|
||||||
|
{
|
||||||
|
$values = array();
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
$values[] = $this->reflFields[$this->fieldNames[$column]]->getValue($entity);
|
||||||
|
}
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates the entity identifier of an entity.
|
* Populates the entity identifier of an entity.
|
||||||
*
|
*
|
||||||
|
@ -221,7 +221,6 @@ class OneToOneMapping extends AssociationMapping
|
|||||||
|
|
||||||
if ($this->isOwningSide) {
|
if ($this->isOwningSide) {
|
||||||
foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
|
foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
|
||||||
// getting customer_id
|
|
||||||
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||||
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||||
} else {
|
} else {
|
||||||
@ -240,7 +239,6 @@ class OneToOneMapping extends AssociationMapping
|
|||||||
$owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName);
|
$owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName);
|
||||||
// TRICKY: since the association is specular source and target are flipped
|
// TRICKY: since the association is specular source and target are flipped
|
||||||
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
|
foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
|
||||||
// getting id
|
|
||||||
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
|
||||||
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
$conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
|
||||||
} else {
|
} else {
|
||||||
@ -250,7 +248,12 @@ class OneToOneMapping extends AssociationMapping
|
|||||||
|
|
||||||
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this);
|
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this);
|
||||||
|
|
||||||
|
if ($targetEntity !== null) {
|
||||||
$targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity);
|
$targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetEntity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
lib/Doctrine/ORM/ORMException.php
Normal file
11
lib/Doctrine/ORM/ORMException.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\ORM;
|
||||||
|
|
||||||
|
class ORMException extends \Exception
|
||||||
|
{
|
||||||
|
public static function entityMissingAssignedId($entity)
|
||||||
|
{
|
||||||
|
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,7 @@ use Doctrine\Common\DoctrineException,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all EntityPersisters. An EntityPersister is a class that knows
|
* Base class for all EntityPersisters. An EntityPersister is a class that knows
|
||||||
* how to persist (and to some extent how to load) entities of a specific type.
|
* how to persist and load entities of a specific type.
|
||||||
*
|
*
|
||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||||
@ -410,6 +410,13 @@ class StandardEntityPersister
|
|||||||
public function load(array $criteria, $entity = null, $assoc = null)
|
public function load(array $criteria, $entity = null, $assoc = null)
|
||||||
{
|
{
|
||||||
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria, $assoc));
|
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria, $assoc));
|
||||||
|
if ($stmt === null) {
|
||||||
|
try {
|
||||||
|
throw new \Exception();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
var_dump($e->getTraceAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
$stmt->execute(array_values($criteria));
|
$stmt->execute(array_values($criteria));
|
||||||
$result = $stmt->fetch(Connection::FETCH_ASSOC);
|
$result = $stmt->fetch(Connection::FETCH_ASSOC);
|
||||||
$stmt->closeCursor();
|
$stmt->closeCursor();
|
||||||
@ -417,6 +424,88 @@ class StandardEntityPersister
|
|||||||
return $this->_createEntity($result, $entity);
|
return $this->_createEntity($result, $entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes an entity.
|
||||||
|
*
|
||||||
|
* @param array $id The identifier of the entity as an associative array from column names to values.
|
||||||
|
* @param object $entity The entity to refresh.
|
||||||
|
*/
|
||||||
|
public function refresh(array $id, $entity)
|
||||||
|
{
|
||||||
|
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($id));
|
||||||
|
$stmt->execute(array_values($id));
|
||||||
|
$result = $stmt->fetch(Connection::FETCH_ASSOC);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
$metaColumns = array();
|
||||||
|
$newData = array();
|
||||||
|
|
||||||
|
// Refresh simple state
|
||||||
|
foreach ($result as $column => $value) {
|
||||||
|
$column = $this->_class->resultColumnNames[$column];
|
||||||
|
if (isset($this->_class->fieldNames[$column])) {
|
||||||
|
$fieldName = $this->_class->fieldNames[$column];
|
||||||
|
$type = Type::getType($this->_class->fieldMappings[$fieldName]['type']);
|
||||||
|
$newValue = $type->convertToPHPValue($value, $this->_platform);
|
||||||
|
$this->_class->reflFields[$fieldName]->setValue($entity, $newValue);
|
||||||
|
$newData[$fieldName] = $newValue;
|
||||||
|
} else {
|
||||||
|
$metaColumns[$column] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh associations
|
||||||
|
foreach ($this->_class->associationMappings as $field => $assoc) {
|
||||||
|
$value = $this->_class->reflFields[$field]->getValue($entity);
|
||||||
|
if ($assoc->isOneToOne()) {
|
||||||
|
if ($value instanceof Proxy && ! $value->__isInitialized()) {
|
||||||
|
continue; // skip uninitialized proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assoc->isOwningSide) {
|
||||||
|
$joinColumnValues = array();
|
||||||
|
$targetColumns = array();
|
||||||
|
foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $srcColumn) {
|
||||||
|
if ($metaColumns[$srcColumn] !== null) {
|
||||||
|
$joinColumnValues[] = $metaColumns[$srcColumn];
|
||||||
|
}
|
||||||
|
$targetColumns[] = $targetColumn;
|
||||||
|
}
|
||||||
|
if ( ! $joinColumnValues && $value !== null) {
|
||||||
|
$this->_class->reflFields[$field]->setValue($entity, null);
|
||||||
|
$newData[$field] = null;
|
||||||
|
} else if ($value !== null) {
|
||||||
|
// Check identity map first, if the entity is not there,
|
||||||
|
// place a proxy in there instead.
|
||||||
|
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
|
||||||
|
if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) {
|
||||||
|
$this->_class->reflFields[$field]->setValue($entity, $found);
|
||||||
|
// Complete inverse side, if necessary.
|
||||||
|
if (isset($targetClass->inverseMappings[$this->_class->name][$field])) {
|
||||||
|
$inverseAssoc = $targetClass->inverseMappings[$this->_class->name][$field];
|
||||||
|
$targetClass->reflFields[$inverseAssoc->sourceFieldName]->setValue($found, $entity);
|
||||||
|
}
|
||||||
|
$newData[$field] = $found;
|
||||||
|
} else if ((array)$this->_class->getIdentifierValues($value) != $joinColumnValues) {
|
||||||
|
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues);
|
||||||
|
$this->_class->reflFields[$field]->setValue($entity, $proxy);
|
||||||
|
$newData[$field] = $proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Inverse side of 1-1/1-x can never be lazy
|
||||||
|
$assoc->load($entity, null, $this->_em);
|
||||||
|
$newData[$field] = $this->_class->reflFields[$field]->getValue($entity);
|
||||||
|
}
|
||||||
|
} else if ($value instanceof PersistentCollection && $value->isInitialized()) {
|
||||||
|
$value->setInitialized(false);
|
||||||
|
$newData[$field] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_em->getUnitOfWork()->setOriginalEntityData($entity, $newData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all entities by a list of field criteria.
|
* Loads all entities by a list of field criteria.
|
||||||
*
|
*
|
||||||
|
@ -1443,15 +1443,12 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$visited[$oid] = $entity; // mark visited
|
$visited[$oid] = $entity; // mark visited
|
||||||
|
|
||||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||||
switch ($this->getEntityState($entity)) {
|
if ($this->getEntityState($entity) == self::STATE_MANAGED) {
|
||||||
case self::STATE_MANAGED:
|
$this->getEntityPersister($class->name)->refresh(
|
||||||
$this->getEntityPersister($class->name)->load(
|
array_combine($class->getIdentifierColumnNames(), $this->_entityIdentifiers[$oid]),
|
||||||
array_combine($class->identifier, $this->_entityIdentifiers[$oid]),
|
|
||||||
$entity
|
$entity
|
||||||
);
|
);
|
||||||
//TODO: refresh (initialized) associations
|
} else {
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new \InvalidArgumentException("Entity is not MANAGED.");
|
throw new \InvalidArgumentException("Entity is not MANAGED.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1651,6 +1648,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
* @param array $data The data for the entity.
|
* @param array $data The data for the entity.
|
||||||
* @return object The created entity instance.
|
* @return object The created entity instance.
|
||||||
* @internal Highly performance-sensitive method.
|
* @internal Highly performance-sensitive method.
|
||||||
|
* @todo Rename: getOrCreateEntity
|
||||||
*/
|
*/
|
||||||
public function createEntity($className, array $data, &$hints = array())
|
public function createEntity($className, array $data, &$hints = array())
|
||||||
{
|
{
|
||||||
@ -1698,25 +1696,31 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
// Properly initialize any unfetched associations, if partial objects are not allowed.
|
// Properly initialize any unfetched associations, if partial objects are not allowed.
|
||||||
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
|
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
|
||||||
foreach ($class->associationMappings as $field => $assoc) {
|
foreach ($class->associationMappings as $field => $assoc) {
|
||||||
// Check if the association is not among the fetch-joined associatons already.
|
// Check if the association is not among the fetch-joined associations already.
|
||||||
if ( ! isset($hints['fetched'][$className][$field])) {
|
if ( ! isset($hints['fetched'][$className][$field])) {
|
||||||
if ($assoc->isOneToOne()) {
|
if ($assoc->isOneToOne()) {
|
||||||
$joinColumns = array();
|
|
||||||
if ($assoc->isOwningSide) {
|
if ($assoc->isOwningSide) {
|
||||||
|
$joinColumns = array();
|
||||||
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
|
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
|
||||||
$joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
|
$joinColumnValue = $data[$assoc->joinColumnFieldNames[$srcColumn]];
|
||||||
|
if ($joinColumnValue !== null) {
|
||||||
|
$joinColumns[$srcColumn] = $joinColumnValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ( ! $joinColumns) {
|
||||||
|
$class->reflFields[$field]->setValue($entity, null);
|
||||||
|
$this->_originalEntityData[$oid][$field] = null;
|
||||||
|
} else {
|
||||||
//TODO: If its in the identity map just get it from there if possible!
|
//TODO: If its in the identity map just get it from there if possible!
|
||||||
if ($assoc->isLazilyFetched() /*&& $assoc->isOwningSide)*/) {
|
|
||||||
// Inject proxy
|
// Inject proxy
|
||||||
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns);
|
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns);
|
||||||
$this->_originalEntityData[$oid][$field] = $proxy;
|
$this->_originalEntityData[$oid][$field] = $proxy;
|
||||||
$class->reflFields[$field]->setValue($entity, $proxy);
|
$class->reflFields[$field]->setValue($entity, $proxy);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Eager load
|
// Inverse side can never be lazy.
|
||||||
//TODO: Allow more efficient and configurable batching of these loads
|
$targetEntity = $assoc->load($entity, new $assoc->targetEntityName, $this->_em);
|
||||||
$assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns);
|
$class->reflFields[$field]->setValue($entity, $targetEntity);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Inject collection
|
// Inject collection
|
||||||
@ -1779,6 +1783,14 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
public function setOriginalEntityData($entity, array $data)
|
||||||
|
{
|
||||||
|
$this->_originalEntityData[spl_object_hash($entity)] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERNAL:
|
* INTERNAL:
|
||||||
* Sets a property value of the original data array of an entity.
|
* Sets a property value of the original data array of an entity.
|
||||||
|
@ -6,12 +6,12 @@ require_once __DIR__ . '/../TestInit.php';
|
|||||||
|
|
||||||
class DoctrineExceptionTest extends \Doctrine\Tests\DoctrineTestCase
|
class DoctrineExceptionTest extends \Doctrine\Tests\DoctrineTestCase
|
||||||
{
|
{
|
||||||
public function testStaticCall()
|
/*public function testStaticCall()
|
||||||
{
|
{
|
||||||
$e = \Doctrine\Common\DoctrineException::testingStaticCallBuildsErrorMessageWithParams('param1', 'param2');
|
$e = \Doctrine\Common\DoctrineException::testingStaticCallBuildsErrorMessageWithParams('param1', 'param2');
|
||||||
|
|
||||||
$this->assertEquals($e->getMessage(), "Testing static call builds error message with params ('param1', 'param2')");
|
$this->assertEquals($e->getMessage(), "Testing static call builds error message with params ('param1', 'param2')");
|
||||||
}
|
}*/
|
||||||
|
|
||||||
public function testInnerException()
|
public function testInnerException()
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,80 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
parent::setUp();
|
parent::setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSingleValuedAssociationIdentityMapBehavior()
|
public function testBasicIdentityManagement()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->status = 'dev';
|
||||||
|
$user->username = 'romanb';
|
||||||
|
$user->name = 'Roman B.';
|
||||||
|
|
||||||
|
$address = new CmsAddress;
|
||||||
|
$address->country = 'de';
|
||||||
|
$address->zip = 1234;
|
||||||
|
$address->city = 'Berlin';
|
||||||
|
|
||||||
|
$user->setAddress($address);
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$user2 = $this->_em->find(get_class($user), $user->getId());
|
||||||
|
$this->assertTrue($user2 !== $user);
|
||||||
|
$user3 = $this->_em->find(get_class($user), $user->getId());
|
||||||
|
$this->assertTrue($user2 === $user3);
|
||||||
|
|
||||||
|
$address2 = $this->_em->find(get_class($address), $address->getId());
|
||||||
|
$this->assertTrue($address2 !== $address);
|
||||||
|
$address3 = $this->_em->find(get_class($address), $address->getId());
|
||||||
|
$this->assertTrue($address2 === $address3);
|
||||||
|
|
||||||
|
$this->assertTrue($user2->getAddress() === $address2); // !!!
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleValuedAssociationIdentityMapBehaviorWithRefresh()
|
||||||
|
{
|
||||||
|
$address = new CmsAddress;
|
||||||
|
$address->country = 'de';
|
||||||
|
$address->zip = '12345';
|
||||||
|
$address->city = 'Berlin';
|
||||||
|
|
||||||
|
$user1 = new CmsUser;
|
||||||
|
$user1->status = 'dev';
|
||||||
|
$user1->username = 'romanb';
|
||||||
|
$user1->name = 'Roman B.';
|
||||||
|
|
||||||
|
$user2 = new CmsUser;
|
||||||
|
$user2->status = 'dev';
|
||||||
|
$user2->username = 'gblanco';
|
||||||
|
$user2->name = 'Guilherme Blanco';
|
||||||
|
|
||||||
|
$address->setUser($user1);
|
||||||
|
|
||||||
|
$this->_em->persist($address);
|
||||||
|
$this->_em->persist($user1);
|
||||||
|
$this->_em->persist($user2);
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$this->assertSame($user1, $address->user);
|
||||||
|
|
||||||
|
//external update to CmsAddress
|
||||||
|
$this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId()));
|
||||||
|
|
||||||
|
// But we want to have this external change!
|
||||||
|
// Solution 1: refresh(), broken atm!
|
||||||
|
$this->_em->refresh($address);
|
||||||
|
|
||||||
|
// Now the association should be "correct", referencing $user2
|
||||||
|
$this->assertSame($user2, $address->user);
|
||||||
|
$this->assertSame($user2->address, $address); // check back reference also
|
||||||
|
|
||||||
|
// Attention! refreshes can result in broken bidirectional associations! this is currently expected!
|
||||||
|
// $user1 still points to $address!
|
||||||
|
$this->assertSame($user1->address, $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleValuedAssociationIdentityMapBehaviorWithRefreshQuery()
|
||||||
{
|
{
|
||||||
$address = new CmsAddress;
|
$address = new CmsAddress;
|
||||||
$address->country = 'de';
|
$address->country = 'de';
|
||||||
@ -65,8 +138,6 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertTrue($user2->address === null);
|
$this->assertTrue($user2->address === null);
|
||||||
|
|
||||||
// But we want to have this external change!
|
// But we want to have this external change!
|
||||||
// Solution 1: refresh(), broken atm!
|
|
||||||
//$this->_em->refresh($address2);
|
|
||||||
// Solution 2: Alternatively, a refresh query should work
|
// Solution 2: Alternatively, a refresh query should work
|
||||||
$q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
|
$q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
|
||||||
$q->setHint(Query::HINT_REFRESH, true);
|
$q->setHint(Query::HINT_REFRESH, true);
|
||||||
@ -83,7 +154,7 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertSame($user1->address, $address2);
|
$this->assertSame($user1->address, $address2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCollectionValuedAssociationIdentityMapBehavior()
|
public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery()
|
||||||
{
|
{
|
||||||
$user = new CmsUser;
|
$user = new CmsUser;
|
||||||
$user->status = 'dev';
|
$user->status = 'dev';
|
||||||
@ -133,5 +204,52 @@ class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
// Now the collection should be refreshed with correct count
|
// Now the collection should be refreshed with correct count
|
||||||
$this->assertEquals(4, count($user3->getPhonenumbers()));
|
$this->assertEquals(4, count($user3->getPhonenumbers()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->status = 'dev';
|
||||||
|
$user->username = 'romanb';
|
||||||
|
$user->name = 'Roman B.';
|
||||||
|
|
||||||
|
$phone1 = new CmsPhonenumber;
|
||||||
|
$phone1->phonenumber = 123;
|
||||||
|
|
||||||
|
$phone2 = new CmsPhonenumber;
|
||||||
|
$phone2->phonenumber = 234;
|
||||||
|
|
||||||
|
$phone3 = new CmsPhonenumber;
|
||||||
|
$phone3->phonenumber = 345;
|
||||||
|
|
||||||
|
$user->addPhonenumber($phone1);
|
||||||
|
$user->addPhonenumber($phone2);
|
||||||
|
$user->addPhonenumber($phone3);
|
||||||
|
|
||||||
|
$this->_em->persist($user); // cascaded to phone numbers
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$this->assertEquals(3, count($user->getPhonenumbers()));
|
||||||
|
|
||||||
|
//external update to CmsAddress
|
||||||
|
$this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId()));
|
||||||
|
|
||||||
|
//select
|
||||||
|
$q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
|
||||||
|
$user2 = $q->getSingleResult();
|
||||||
|
|
||||||
|
$this->assertSame($user, $user2);
|
||||||
|
|
||||||
|
// Should still be the same 3 phonenumbers
|
||||||
|
$this->assertEquals(3, count($user2->getPhonenumbers()));
|
||||||
|
|
||||||
|
// But we want to have this external change!
|
||||||
|
// Solution 1: refresh().
|
||||||
|
$this->_em->refresh($user2);
|
||||||
|
|
||||||
|
$this->assertSame($user, $user2); // should still be the same, always from identity map
|
||||||
|
|
||||||
|
// Now the collection should be refreshed with correct count
|
||||||
|
$this->assertEquals(4, count($user2->getPhonenumbers()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1);
|
$e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1);
|
||||||
$this->assertEquals(1, $e2->getId());
|
$this->assertEquals(1, $e2->getId());
|
||||||
$this->assertEquals('Roman', $e2->getName());
|
$this->assertEquals('Roman', $e2->getName());
|
||||||
$this->assertTrue($e2->getMappedRelated1() instanceof MappedSuperclassRelated1);
|
$this->assertNull($e2->getMappedRelated1());
|
||||||
$this->assertEquals(42, $e2->getMapped1());
|
$this->assertEquals(42, $e2->getMapped1());
|
||||||
$this->assertEquals('bar', $e2->getMapped2());
|
$this->assertEquals('bar', $e2->getMapped2());
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
|
|||||||
$this->assertEquals('Giorgio', $cart->getCustomer()->getName());
|
$this->assertEquals('Giorgio', $cart->getCustomer()->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLazyLoadsObjectsOnTheInverseSide()
|
public function testInverseSideIsNeverLazy()
|
||||||
{
|
{
|
||||||
$this->_createFixture();
|
$this->_createFixture();
|
||||||
$metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer');
|
$metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer');
|
||||||
@ -90,7 +90,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
|
|||||||
|
|
||||||
$this->assertNull($customer->getMentor());
|
$this->assertNull($customer->getMentor());
|
||||||
$this->assertTrue($customer->getCart() instanceof ECommerceCart);
|
$this->assertTrue($customer->getCart() instanceof ECommerceCart);
|
||||||
$this->assertTrue($customer->getCart() instanceof \Doctrine\ORM\Proxy\Proxy);
|
$this->assertFalse($customer->getCart() instanceof \Doctrine\ORM\Proxy\Proxy);
|
||||||
$this->assertEquals('paypal', $customer->getCart()->getPayment());
|
$this->assertEquals('paypal', $customer->getCart()->getPayment());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ namespace Doctrine\Tests\ORM\Performance;
|
|||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
use Doctrine\Tests\Mocks\HydratorMockStatement;
|
use Doctrine\Tests\Mocks\HydratorMockStatement,
|
||||||
use Doctrine\ORM\Query\ResultSetMapping;
|
Doctrine\ORM\Query\ResultSetMapping,
|
||||||
use Doctrine\ORM\Query;
|
Doctrine\ORM\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests to prevent serious performance regressions.
|
* Tests to prevent serious performance regressions.
|
||||||
@ -18,6 +18,64 @@ use Doctrine\ORM\Query;
|
|||||||
*/
|
*/
|
||||||
class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Times for comparison:
|
||||||
|
*
|
||||||
|
* [romanb: 10000 rows => 0.7 seconds]
|
||||||
|
*
|
||||||
|
* MAXIMUM TIME: 1 second
|
||||||
|
*/
|
||||||
|
public function testSimpleQueryScalarHydrationPerformance10000Rows()
|
||||||
|
{
|
||||||
|
$rsm = new ResultSetMapping;
|
||||||
|
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
|
||||||
|
$rsm->addFieldResult('u', 'u__id', 'id');
|
||||||
|
$rsm->addFieldResult('u', 'u__status', 'status');
|
||||||
|
$rsm->addFieldResult('u', 'u__username', 'username');
|
||||||
|
$rsm->addFieldResult('u', 'u__name', 'name');
|
||||||
|
|
||||||
|
// Faked result set
|
||||||
|
$resultSet = array(
|
||||||
|
//row1
|
||||||
|
array(
|
||||||
|
'u__id' => '1',
|
||||||
|
'u__status' => 'developer',
|
||||||
|
'u__username' => 'romanb',
|
||||||
|
'u__name' => 'Roman',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'u__id' => '1',
|
||||||
|
'u__status' => 'developer',
|
||||||
|
'u__username' => 'romanb',
|
||||||
|
'u__name' => 'Roman',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'u__id' => '2',
|
||||||
|
'u__status' => 'developer',
|
||||||
|
'u__username' => 'romanb',
|
||||||
|
'u__name' => 'Roman',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for ($i = 4; $i < 10000; ++$i) {
|
||||||
|
$resultSet[] = array(
|
||||||
|
'u__id' => $i,
|
||||||
|
'u__status' => 'developer',
|
||||||
|
'u__username' => 'jwage',
|
||||||
|
'u__name' => 'Jonathan',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = new HydratorMockStatement($resultSet);
|
||||||
|
$hydrator = new \Doctrine\ORM\Internal\Hydration\ScalarHydrator($this->_em);
|
||||||
|
|
||||||
|
$this->setMaxRunningTime(1);
|
||||||
|
$s = microtime(true);
|
||||||
|
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||||
|
$e = microtime(true);
|
||||||
|
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Times for comparison:
|
* Times for comparison:
|
||||||
*
|
*
|
||||||
@ -219,6 +277,16 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
$rsm->addFieldResult('u', 'u__status', 'status');
|
$rsm->addFieldResult('u', 'u__status', 'status');
|
||||||
$rsm->addFieldResult('u', 'u__username', 'username');
|
$rsm->addFieldResult('u', 'u__username', 'username');
|
||||||
$rsm->addFieldResult('u', 'u__name', 'name');
|
$rsm->addFieldResult('u', 'u__name', 'name');
|
||||||
|
$rsm->addJoinedEntityResult(
|
||||||
|
'Doctrine\Tests\Models\CMS\CmsAddress',
|
||||||
|
'a',
|
||||||
|
'u',
|
||||||
|
'address'
|
||||||
|
);
|
||||||
|
$rsm->addFieldResult('a', 'a__id', 'id');
|
||||||
|
//$rsm->addFieldResult('a', 'a__country', 'country');
|
||||||
|
//$rsm->addFieldResult('a', 'a__zip', 'zip');
|
||||||
|
//$rsm->addFieldResult('a', 'a__city', 'city');
|
||||||
|
|
||||||
// Faked result set
|
// Faked result set
|
||||||
$resultSet = array(
|
$resultSet = array(
|
||||||
@ -228,27 +296,17 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
'u__status' => 'developer',
|
'u__status' => 'developer',
|
||||||
'u__username' => 'romanb',
|
'u__username' => 'romanb',
|
||||||
'u__name' => 'Roman',
|
'u__name' => 'Roman',
|
||||||
),
|
'a__id' => '1'
|
||||||
array(
|
|
||||||
'u__id' => '1',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'u__id' => '2',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
for ($i = 4; $i < 10000; ++$i) {
|
for ($i = 2; $i < 10000; ++$i) {
|
||||||
$resultSet[] = array(
|
$resultSet[] = array(
|
||||||
'u__id' => $i,
|
'u__id' => $i,
|
||||||
'u__status' => 'developer',
|
'u__status' => 'developer',
|
||||||
'u__username' => 'jwage',
|
'u__username' => 'jwage',
|
||||||
'u__name' => 'Jonathan',
|
'u__name' => 'Jonathan',
|
||||||
|
'a__id' => $i
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,6 +413,13 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
$rsm->addFieldResult('u', 'u__name', 'name');
|
$rsm->addFieldResult('u', 'u__name', 'name');
|
||||||
$rsm->addScalarResult('sclr0', 'nameUpper');
|
$rsm->addScalarResult('sclr0', 'nameUpper');
|
||||||
$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber');
|
$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber');
|
||||||
|
$rsm->addJoinedEntityResult(
|
||||||
|
'Doctrine\Tests\Models\CMS\CmsAddress',
|
||||||
|
'a',
|
||||||
|
'u',
|
||||||
|
'address'
|
||||||
|
);
|
||||||
|
$rsm->addFieldResult('a', 'a__id', 'id');
|
||||||
|
|
||||||
// Faked result set
|
// Faked result set
|
||||||
$resultSet = array(
|
$resultSet = array(
|
||||||
@ -366,33 +431,19 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
'u__name' => 'Roman',
|
'u__name' => 'Roman',
|
||||||
'sclr0' => 'ROMANB',
|
'sclr0' => 'ROMANB',
|
||||||
'p__phonenumber' => '42',
|
'p__phonenumber' => '42',
|
||||||
),
|
'a__id' => '1'
|
||||||
array(
|
|
||||||
'u__id' => '1',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
'sclr0' => 'ROMANB',
|
|
||||||
'p__phonenumber' => '43',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'u__id' => '2',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
'sclr0' => 'JWAGE',
|
|
||||||
'p__phonenumber' => '91'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
for ($i = 4; $i < 2000; ++$i) {
|
for ($i = 2; $i < 2000; ++$i) {
|
||||||
$resultSet[] = array(
|
$resultSet[] = array(
|
||||||
'u__id' => $i,
|
'u__id' => $i,
|
||||||
'u__status' => 'developer',
|
'u__status' => 'developer',
|
||||||
'u__username' => 'jwage',
|
'u__username' => 'jwage',
|
||||||
'u__name' => 'Jonathan',
|
'u__name' => 'Jonathan',
|
||||||
'sclr0' => 'JWAGE' . $i,
|
'sclr0' => 'JWAGE' . $i,
|
||||||
'p__phonenumber' => '91'
|
'p__phonenumber' => '91',
|
||||||
|
'a__id' => $i
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,63 +456,5 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
|
|||||||
$e = microtime(true);
|
$e = microtime(true);
|
||||||
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
|
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Times for comparison:
|
|
||||||
*
|
|
||||||
* [romanb: 10000 rows => 0.7 seconds]
|
|
||||||
*
|
|
||||||
* MAXIMUM TIME: 1 second
|
|
||||||
*/
|
|
||||||
public function testSimpleQueryScalarHydrationPerformance10000Rows()
|
|
||||||
{
|
|
||||||
$rsm = new ResultSetMapping;
|
|
||||||
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
|
|
||||||
$rsm->addFieldResult('u', 'u__id', 'id');
|
|
||||||
$rsm->addFieldResult('u', 'u__status', 'status');
|
|
||||||
$rsm->addFieldResult('u', 'u__username', 'username');
|
|
||||||
$rsm->addFieldResult('u', 'u__name', 'name');
|
|
||||||
|
|
||||||
// Faked result set
|
|
||||||
$resultSet = array(
|
|
||||||
//row1
|
|
||||||
array(
|
|
||||||
'u__id' => '1',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'u__id' => '1',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'u__id' => '2',
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'romanb',
|
|
||||||
'u__name' => 'Roman',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
for ($i = 4; $i < 10000; ++$i) {
|
|
||||||
$resultSet[] = array(
|
|
||||||
'u__id' => $i,
|
|
||||||
'u__status' => 'developer',
|
|
||||||
'u__username' => 'jwage',
|
|
||||||
'u__name' => 'Jonathan',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = new HydratorMockStatement($resultSet);
|
|
||||||
$hydrator = new \Doctrine\ORM\Internal\Hydration\ScalarHydrator($this->_em);
|
|
||||||
|
|
||||||
$this->setMaxRunningTime(1);
|
|
||||||
$s = microtime(true);
|
|
||||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
|
||||||
$e = microtime(true);
|
|
||||||
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user