1
0
mirror of synced 2025-02-20 22:23:14 +03:00

Merge branch 'master' of github.com:doctrine/doctrine2

This commit is contained in:
Guilherme Blanco 2011-03-19 14:36:29 -03:00
commit 8c8a658dfd
25 changed files with 959 additions and 270 deletions

View File

@ -57,6 +57,11 @@ abstract class AbstractQuery
*/
const HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
const HYDRATE_SIMPLEOBJECT = 5;
/**
* @var array The parameter map of this query.
*/
@ -331,6 +336,26 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
/**
* Change the default fetch mode of an association for this query.
*
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
*
* @param string $class
* @param string $assocName
* @param int $fetchMode
* @return AbstractQuery
*/
public function setFetchMode($class, $assocName, $fetchMode)
{
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
}
$this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
return $this;
}
/**
* Defines the processing mode to be used during hydration / result set transformation.
*

View File

@ -679,6 +679,9 @@ class EntityManager implements ObjectManager
case Query::HYDRATE_SINGLE_SCALAR:
$hydrator = new Internal\Hydration\SingleScalarHydrator($this);
break;
case Query::HYDRATE_SIMPLEOBJECT:
$hydrator = new Internal\Hydration\SimpleObjectHydrator($this);
break;
default:
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
$hydrator = new $class($this);

View File

@ -211,7 +211,16 @@ abstract class AbstractHydrator
}
if (isset($cache[$key]['isMetaColumn'])) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
}
continue;
}
// in an inheritance hierachy the same field could be defined several times.
// We overwrite this value so long we dont have a non-null value, that value we keep.
// Per definition it cannot be that a field is defined several times and has several values.
if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
continue;
}

View File

@ -59,6 +59,9 @@ class ObjectHydrator extends AbstractHydrator
$this->_resultPointers =
$this->_idTemplate = array();
$this->_resultCounter = 0;
if (!isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true;
}
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array();
@ -108,11 +111,17 @@ class ObjectHydrator extends AbstractHydrator
*/
protected function _cleanup()
{
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
parent::_cleanup();
$this->_identifierMap =
$this->_initializedCollections =
$this->_existingCollections =
$this->_resultPointers = array();
if ($eagerLoad) {
$this->_em->getUnitOfWork()->triggerEagerLoads();
}
}
/**
@ -395,6 +404,10 @@ class ObjectHydrator extends AbstractHydrator
$result[$key] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
}
if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateSet($key, $element);
}
} else {
if ($this->_rsm->isMixed) {
$element = array(0 => $element);
@ -402,6 +415,10 @@ class ObjectHydrator extends AbstractHydrator
$result[] = $element;
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
if (isset($this->_hints['collection'])) {
$this->_hints['collection']->hydrateAdd($element);
}
}
// Update result pointer

View File

@ -0,0 +1,141 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Internal\Hydration;
use \PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
class SimpleObjectHydrator extends AbstractHydrator
{
const REFRESH_ENTITY = 'doctrine_refresh_entity';
/**
* @var ClassMetadata
*/
private $class;
private $declaringClasses = array();
protected function _hydrateAll()
{
$result = array();
$cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($row, $cache, $result);
}
$this->_em->getUnitOfWork()->triggerEagerLoads();
return $result;
}
protected function _prepare()
{
if (count($this->_rsm->aliasMap) == 1) {
$this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap));
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
foreach ($this->_rsm->declaringClasses AS $column => $class) {
$this->declaringClasses[$column] = $this->_em->getClassMetadata($class);
}
}
} else {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result.");
}
if ($this->_rsm->scalarMappings) {
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
}
}
protected function _hydrateRow(array $sqlResult, array &$cache, array &$result)
{
$data = array();
if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) {
foreach ($sqlResult as $column => $value) {
if (!isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) {
$cache[$column]['name'] = $this->_rsm->fieldMappings[$column];
$cache[$column]['field'] = true;
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
}
if (isset($cache[$column]['field'])) {
$value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
}
$data[$cache[$column]['name']] = $value;
}
$entityName = $this->class->name;
} else {
$discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']);
$entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) {
if (!isset($cache[$column])) {
if (isset($this->_rsm->fieldMappings[$column])) {
$field = $this->_rsm->fieldMappings[$column];
$class = $this->declaringClasses[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$cache[$column]['name'] = $field;
$cache[$column]['class'] = $class;
}
} else if (isset($this->_rsm->relationMap[$column])) {
if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) {
$cache[$column]['name'] = $field;
}
} else {
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
}
}
if (isset($cache[$column]['class'])) {
$value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type'])
->convertToPHPValue($value, $this->_platform);
}
// the second and part is to prevent overwrites in case of multiple
// inheritance classes using the same property name (See AbstractHydrator)
if (isset($cache[$column]) && (!isset($data[$cache[$column]['name']]) || $value !== null)) {
$data[$cache[$column]['name']] = $value;
}
}
}
if (isset($this->_hints[self::REFRESH_ENTITY])) {
$this->_hints[Query::HINT_REFRESH] = true;
$id = array();
if ($this->_class->isIdentifierComposite) {
foreach ($this->_class->identifier as $fieldName) {
$id[$fieldName] = $data[$fieldName];
}
} else {
$id = array($this->_class->identifier[0] => $data[$this->_class->identifier[0]]);
}
$this->_em->getUnitOfWork()->registerManaged($this->_hints[self::REFRESH_ENTITY], $id, $data);
}
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
}
}

View File

@ -28,24 +28,11 @@ use Doctrine\ORM\Mapping\ClassMetadata,
* types in the hierarchy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{
/**
* Map from column names to class metadata instances that declare the field the column is mapped to.
*
* @var array
*/
private $declaringClassMap = array();
/**
* Map from column names to class names that declare the field the association with join column is mapped to.
*
* @var array
*/
private $declaringJoinColumnMap = array();
/**
* {@inheritdoc}
*/
@ -69,49 +56,12 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
/**
* {@inheritdoc}
*/
protected function _processSQLResult(array $sqlResult)
{
$data = array();
$discrColumnName = $this->_platform->getSQLResultCasing($this->_class->discriminatorColumn['name']);
$entityName = $this->_class->discriminatorMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) {
$realColumnName = $this->_resultColumnNames[$column];
if (isset($this->declaringClassMap[$column])) {
$class = $this->declaringClassMap[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$field = $class->fieldNames[$realColumnName];
if (isset($data[$field])) {
$data[$realColumnName] = $value;
} else {
$data[$field] = Type::getType($class->fieldMappings[$field]['type'])
->convertToPHPValue($value, $this->_platform);
}
}
} else if (isset($this->declaringJoinColumnMap[$column])) {
if ($this->declaringJoinColumnMap[$column] == $entityName || is_subclass_of($entityName, $this->declaringJoinColumnMap[$column])) {
$data[$realColumnName] = $value;
}
} else {
$data[$realColumnName] = $value;
}
}
return array($entityName, $data);
}
/**
* {@inheritdoc}
*/
protected function _getSelectColumnSQL($field, ClassMetadata $class)
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
{
$columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
if ( ! isset($this->_resultColumnNames[$columnAlias])) {
$this->_resultColumnNames[$columnAlias] = $columnName;
$this->declaringClassMap[$columnAlias] = $class;
}
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
return "$sql AS $columnAlias";
}
@ -120,10 +70,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{
$columnAlias = $joinColumnName . $this->_sqlAliasCounter++;
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $joinColumnName;
$this->declaringJoinColumnMap[$resultColumnName] = $className;
}
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
return $tableAlias . ".$joinColumnName AS $columnAlias";
}

View File

@ -22,6 +22,7 @@ namespace Doctrine\ORM\Persisters;
use PDO,
Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\DBAL\Connection,
Doctrine\ORM\ORMException,
Doctrine\ORM\OptimisticLockException,
Doctrine\ORM\EntityManager,
@ -69,6 +70,7 @@ use PDO,
*
* @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class BasicEntityPersister
@ -107,15 +109,15 @@ class BasicEntityPersister
* @var array
*/
protected $_queuedInserts = array();
/**
* Case-sensitive mappings of column names as they appear in an SQL result set
* to column names as they are defined in the mapping. This is necessary because different
* RDBMS vendors return column names in result sets in different casings.
* ResultSetMapping that is used for all queries. Is generated lazily once per request.
*
* @var array
* TODO: Evaluate Caching in combination with the other cached SQL snippets.
*
* @var Query\ResultSetMapping
*/
protected $_resultColumnNames = array();
protected $_rsm;
/**
* The map of column names to DBAL mapping types of all prepared columns used
@ -142,6 +144,14 @@ class BasicEntityPersister
* @var string
*/
protected $_selectColumnListSql;
/**
* The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one
* associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations.
*
* @var string
*/
protected $_selectJoinSql;
/**
* Counter for creating unique SQL table and column aliases.
@ -557,10 +567,18 @@ class BasicEntityPersister
$sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode);
list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
if ($entity !== null) {
$hints[Query::HINT_REFRESH] = true;
}
return $this->_createEntity($result, $entity, $hints);
if ($this->_selectJoinSql) {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
} else {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
}
$entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints);
return $entities ? $entities[0] : null;
}
/**
@ -577,6 +595,10 @@ class BasicEntityPersister
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, $targetEntity, array $identifier = array())
{
if ($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) {
return $foundEntity;
}
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
if ($assoc['isOwningSide']) {
@ -641,79 +663,9 @@ class BasicEntityPersister
$sql = $this->_getSelectEntitiesSQL($id);
list($params, $types) = $this->expandParameters($id);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
$metaColumns = array();
$newData = array();
// Refresh simple state
foreach ($result as $column => $value) {
$column = $this->_resultColumnNames[$column];
if (isset($this->_class->fieldNames[$column])) {
$fieldName = $this->_class->fieldNames[$column];
$newValue = $this->_conn->convertToPHPValue($value, $this->_class->fieldMappings[$fieldName]['type']);
$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['type'] & ClassMetadata::TO_ONE) {
if ($value instanceof Proxy && ! $value->__isInitialized__) {
continue; // skip uninitialized proxies
}
if ($assoc['isOwningSide']) {
$joinColumnValues = array();
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
if ($metaColumns[$srcColumn] !== null) {
$joinColumnValues[$targetColumn] = $metaColumns[$srcColumn];
}
}
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['targetEntity']);
if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) {
$this->_class->reflFields[$field]->setValue($entity, $found);
// Complete inverse side, if necessary.
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($found, $entity);
}
$newData[$field] = $found;
} else {
// FIXME: What is happening with subClassees here?
$proxy = $this->_em->getProxyFactory()->getProxy($assoc['targetEntity'], $joinColumnValues);
$this->_class->reflFields[$field]->setValue($entity, $proxy);
$newData[$field] = $proxy;
$this->_em->getUnitOfWork()->registerManaged($proxy, $joinColumnValues, array());
}
}
} else {
// Inverse side of 1-1/1-x can never be lazy.
//$newData[$field] = $assoc->load($entity, null, $this->_em);
$newData[$field] = $this->_em->getUnitOfWork()->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity, null);
}
} else if ($value instanceof PersistentCollection && $value->isInitialized()) {
$value->setInitialized(false);
// no matter if dirty or non-dirty entities are already loaded, smoke them out!
// the beauty of it being, they are still in the identity map
$value->unwrap()->clear();
$newData[$field] = $value;
}
}
$this->_em->getUnitOfWork()->setOriginalEntityData($entity, $newData);
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
$hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true));
if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) {
$this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity);
@ -736,14 +688,13 @@ class BasicEntityPersister
$sql = $this->_getSelectEntitiesSQL($criteria);
list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($result as $row) {
$entities[] = $this->_createEntity($row);
if ($this->_selectJoinSql) {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
} else {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
}
return $entities;
return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true));
}
/**
@ -770,19 +721,17 @@ class BasicEntityPersister
*/
private function loadArrayFromStatement($assoc, $stmt)
{
$entities = array();
$hints = array('deferEagerLoads' => true);
if (isset($assoc['indexBy'])) {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$entity = $this->_createEntity($result);
$entities[$this->_class->reflFields[$assoc['indexBy']]->getValue($entity)] = $entity;
}
$rsm = clone ($this->_rsm); // this is necessary because the "default rsm" should be changed.
$rsm->addIndexBy('r', $assoc['indexBy']);
} else {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$entities[] = $this->_createEntity($result);
}
$rsm = $this->_rsm;
}
$stmt->closeCursor();
return $entities;
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
return $hydrator->hydrateAll($stmt, $rsm, $hints);
}
/**
@ -793,18 +742,18 @@ class BasicEntityPersister
* @param PersistentCollection $coll
*/
private function loadCollectionFromStatement($assoc, $stmt, $coll)
{
{
$hints = array('deferEagerLoads' => true, 'collection' => $coll);
if (isset($assoc['indexBy'])) {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$entity = $this->_createEntity($result);
$coll->hydrateSet($this->_class->reflFields[$assoc['indexBy']]->getValue($entity), $entity);
}
$rsm = clone ($this->_rsm); // this is necessary because the "default rsm" should be changed.
$rsm->addIndexBy('r', $assoc['indexBy']);
} else {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$coll->hydrateAdd($this->_createEntity($result));
}
$rsm = $this->_rsm;
}
$stmt->closeCursor();
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
$hydrator->hydrateAll($stmt, $rsm, $hints);
}
/**
@ -827,7 +776,6 @@ class BasicEntityPersister
{
$criteria = array();
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
$joinTableConditions = array();
if ($assoc['isOwningSide']) {
foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
if ($sourceClass->containsForeignIdentifier) {
@ -873,72 +821,6 @@ class BasicEntityPersister
return $this->_conn->executeQuery($sql, $params, $types);
}
/**
* Creates or fills a single entity object from an SQL result.
*
* @param $result The SQL result.
* @param object $entity The entity object to fill, if any.
* @param array $hints Hints for entity creation.
* @return object The filled and managed entity object or NULL, if the SQL result is empty.
*/
private function _createEntity($result, $entity = null, array $hints = array())
{
if ($result === false) {
return null;
}
list($entityName, $data) = $this->_processSQLResult($result);
if ($entity !== null) {
$hints[Query::HINT_REFRESH] = true;
$id = array();
if ($this->_class->isIdentifierComposite) {
foreach ($this->_class->identifier as $fieldName) {
$id[$fieldName] = $data[$fieldName];
}
} else {
$id = array($this->_class->identifier[0] => $data[$this->_class->identifier[0]]);
}
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
}
return $this->_em->getUnitOfWork()->createEntity($entityName, $data, $hints);
}
/**
* Processes an SQL result set row that contains data for an entity of the type
* this persister is responsible for.
*
* Subclasses are supposed to override this method if they need to change the
* hydration procedure for entities loaded through basic find operations or
* lazy-loading (not DQL).
*
* @param array $sqlResult The SQL result set row to process.
* @return array A tuple where the first value is the actual type of the entity and
* the second value the prepared data of the entity (a map from field
* names to values).
*/
protected function _processSQLResult(array $sqlResult)
{
$data = array();
foreach ($sqlResult as $column => $value) {
$column = $this->_resultColumnNames[$column];
if (isset($this->_class->fieldNames[$column])) {
$field = $this->_class->fieldNames[$column];
if (isset($data[$field])) {
$data[$column] = $value;
} else {
$data[$field] = Type::getType($this->_class->fieldMappings[$field]['type'])
->convertToPHPValue($value, $this->_platform);
}
} else {
$data[$column] = $value;
}
}
return array($this->_class->name, $data);
}
/**
* Gets the SELECT SQL to select one or more entities by a set of field criteria.
*
@ -970,7 +852,7 @@ class BasicEntityPersister
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL()
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name), $lockMode)
. $joinSql
. $this->_selectJoinSql . $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql, $limit, $offset)
. $lockSql;
@ -1023,6 +905,8 @@ class BasicEntityPersister
}
$columnList = '';
$this->_rsm = new Query\ResultSetMapping();
$this->_rsm->addEntityResult($this->_class->name, 'r'); // r for root
// Add regular columns to select list
foreach ($this->_class->fieldNames as $field) {
@ -1030,16 +914,53 @@ class BasicEntityPersister
$columnList .= $this->_getSelectColumnSQL($field, $this->_class);
}
foreach ($this->_class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
$this->_selectJoinSql = '';
$eagerAliasCounter = 0;
foreach ($this->_class->associationMappings as $assocField => $assoc) {
$assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class);
if ($assocColumnSQL) {
if ($columnList) $columnList .= ', ';
$columnList .= $assocColumnSQL;
}
if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) {
continue; // now this is why you shouldn't use inheritance
}
$assocAlias = 'e' . ($eagerAliasCounter++);
$this->_rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField);
foreach ($eagerEntity->fieldNames AS $field) {
if ($columnList) $columnList .= ', ';
$columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias);
}
foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) {
$assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias);
if ($assoc2ColumnSQL) {
if ($columnList) $columnList .= ', ';
$columnList .= $assoc2ColumnSQL;
}
}
$this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable.
if ($assoc['isOwningSide']) {
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.'.$sourceCol.' = ' .
$this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.'.$targetCol.' ';
}
} else {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
$this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.'.$sourceCol.' = ' .
$this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' ';
}
}
}
@ -1049,6 +970,22 @@ class BasicEntityPersister
return $this->_selectColumnListSql;
}
protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r')
{
$columnList = '';
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList) $columnList .= ', ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
}
}
return $columnList;
}
/**
* Gets the SQL join fragment used when selecting entities from a
@ -1152,15 +1089,14 @@ class BasicEntityPersister
* @param string $field The field name.
* @param ClassMetadata $class The class that declares this field. The table this class is
* mapped to must own the column for the given field.
* @param string $alias
*/
protected function _getSelectColumnSQL($field, ClassMetadata $class)
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
{
$columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform);
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
if ( ! isset($this->_resultColumnNames[$columnAlias])) {
$this->_resultColumnNames[$columnAlias] = $columnName;
}
$this->_rsm->addFieldResult($alias, $columnAlias, $field);
return "$sql AS $columnAlias";
}
@ -1172,14 +1108,18 @@ class BasicEntityPersister
* @return string The SQL table alias.
* @todo Reconsider. Binding table aliases to class names is not such a good idea.
*/
protected function _getSQLTableAlias($className)
protected function _getSQLTableAlias($className, $assocName = '')
{
if ($assocName) {
$className .= '#'.$assocName;
}
if (isset($this->_sqlTableAliases[$className])) {
return $this->_sqlTableAliases[$className];
}
$tableAlias = 't' . $this->_sqlAliasCounter++;
$this->_sqlTableAliases[$className] = $tableAlias;
$this->_sqlTableAliases[$className] = $tableAlias;
return $tableAlias;
}
@ -1265,7 +1205,7 @@ class BasicEntityPersister
} else {
throw ORMException::unrecognizedField($field);
}
$conditionSql .= ' = ?';
$conditionSql .= (is_array($value)) ? ' IN (?)' : ' = ?';
}
return $conditionSql;
}
@ -1330,6 +1270,7 @@ class BasicEntityPersister
$sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset);
list($params, $types) = $this->expandParameters($criteria);
return $this->_conn->executeQuery($sql, $params, $types);
}
@ -1348,6 +1289,10 @@ class BasicEntityPersister
if (isset($this->_class->fieldMappings[$field])) {
$type = Type::getType($this->_class->fieldMappings[$field]['type'])->getBindingType();
}
if (is_array($value)) {
$type += Connection::ARRAY_PARAM_OFFSET;
}
$params[] = $value;
$types[] = $type;
}

View File

@ -21,13 +21,15 @@ namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\ORMException,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\DBAL\LockMode;
Doctrine\DBAL\LockMode,
Doctrine\ORM\Query\ResultSetMapping;
/**
* The joined subclass persister maps a single entity instance to several tables in the
* database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
*/
@ -243,6 +245,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Create the column list fragment only once
if ($this->_selectColumnListSql === null) {
$this->_rsm = new ResultSetMapping();
$this->_rsm->addEntityResult($this->_class->name, 'r');
// Add regular columns
$columnList = '';
foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
@ -278,7 +284,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
}
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
$this->_rsm->setDiscriminatorColumn('r', $discrColumn);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
}
// INNER JOIN parent tables

View File

@ -26,6 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
* SINGLE_TABLE strategy.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
*/
@ -48,7 +49,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
$tableAlias = $this->_getSQLTableAlias($rootClass->name);
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
$this->_rsm->setDiscriminatorColumn('r', $discrColumn);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
// Append subclass columns
foreach ($this->_class->subClasses as $subClassName) {
@ -86,9 +88,9 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
}
/** {@inheritdoc} */
protected function _getSQLTableAlias($className)
protected function _getSQLTableAlias($className, $assocName = '')
{
return parent::_getSQLTableAlias($this->_class->rootEntityName);
return parent::_getSQLTableAlias($this->_class->rootEntityName, $assocName);
}
/** {@inheritdoc} */

View File

@ -218,6 +218,13 @@ class UnitOfWork implements PropertyChangedListener
//private $_readOnlyObjects = array();
/**
* Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
*
* @var array
*/
private $eagerLoadingEntities = array();
/**
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
*
@ -1886,6 +1893,9 @@ class UnitOfWork implements PropertyChangedListener
$class->reflFields[$field]->setValue($entity, $value);
}
}
// Loading the entity right here, if its in the eager loading map get rid of it there.
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
// Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
@ -1900,6 +1910,7 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if ($assoc['isOwningSide']) {
$associatedId = array();
// TODO: Is this even computed right in all cases of composite keys?
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
@ -1915,6 +1926,10 @@ class UnitOfWork implements PropertyChangedListener
$class->reflFields[$field]->setValue($entity, null);
$this->originalEntityData[$oid][$field] = null;
} else {
if (!isset($hints['fetchMode'][$class->name][$field])) {
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
}
// Foreign key is set
// Check identity map first
// FIXME: Can break easily with composite keys if join column values are in
@ -1922,16 +1937,38 @@ class UnitOfWork implements PropertyChangedListener
$relatedIdHash = implode(' ', $associatedId);
if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) {
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
// if this is an uninitialized proxy, we are deferring eager loads,
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
// then we cann append this entity for eager loading!
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
isset($hints['deferEagerLoad']) &&
!$targetClass->isIdentifierComposite &&
$newValue instanceof Proxy &&
$newValue->__isInitialized__ === false) {
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
}
} else {
if ($targetClass->subClasses) {
// If it might be a subtype, it can not be lazy
// If it might be a subtype, it can not be lazy. There isn't even
// a way to solve this with deferred eager loading, which means putting
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
$newValue = $this->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity, null, $associatedId);
} else {
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
// TODO: Maybe it could be optimized to do an eager fetch with a JOIN inside
// the persister instead of this rather unperformant approach.
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
// Deferred eager load only works for single identifier classes
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER) {
if (isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite) {
// TODO: Is there a faster approach?
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
} else {
// TODO: This is very imperformant, ignore it?
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
}
} else {
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
}
@ -1945,6 +1982,11 @@ class UnitOfWork implements PropertyChangedListener
}
$this->originalEntityData[$oid][$field] = $newValue;
$class->reflFields[$field]->setValue($entity, $newValue);
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
}
}
} else {
// Inverse side of x-to-one can never be lazy
@ -1955,10 +1997,10 @@ class UnitOfWork implements PropertyChangedListener
// Inject collection
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
$pColl->setOwner($entity, $assoc);
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, $pColl);
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
@ -1982,6 +2024,25 @@ class UnitOfWork implements PropertyChangedListener
return $entity;
}
/**
* @return void
*/
public function triggerEagerLoads()
{
if (!$this->eagerLoadingEntities) {
return;
}
// avoid infinite recursion
$eagerLoadingEntities = $this->eagerLoadingEntities;
$this->eagerLoadingEntities = array();
foreach ($eagerLoadingEntities AS $entityName => $ids) {
$class = $this->em->getClassMetadata($entityName);
$this->getEntityPersister($entityName)->loadAll(array_combine($class->identifier, array(array_values($ids))));
}
}
/**
* Initializes (loads) an uninitialized persistent collection of an entity.
*

@ -1 +1 @@
Subproject commit 556351d9d6b4a33506f2c1535cccee34faa65d62
Subproject commit 9cd6df38d841abb4719286ea35a1b37aa2679f8d

View File

@ -35,6 +35,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\AdvancedDqlQueryTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToOneUnidirectionalAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToOneBidirectionalAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToOneEagerLoadingTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManyBidirectionalAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManyUnidirectionalAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManyBasicAssociationTest');

View File

@ -946,4 +946,35 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNull($this->_em->find(get_class($ph), $ph->phonenumber)->getUser());
}
/**
* @group DDC-952
*/
public function testManyToOneFetchModeQuery()
{
$user = new CmsUser();
$user->username = "beberlei";
$user->name = "Benjamin E.";
$user->status = 'active';
$article = new CmsArticle();
$article->topic = "foo";
$article->text = "bar";
$article->user = $user;
$this->_em->persist($article);
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$qc = $this->getCurrentQueryCount();
$dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.id = ?1";
$article = $this->_em->createQuery($dql)
->setParameter(1, $article->id)
->setFetchMode('Doctrine\Tests\Models\CMS\CmsArticle', 'user', \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER)
->getSingleResult();
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $article->user, "It IS a proxy, ...");
$this->assertTrue($article->user->__isInitialized__, "...but its initialized!");
$this->assertEquals($qc+2, $this->getCurrentQueryCount());
}
}

View File

@ -172,10 +172,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$userId = $user->id;
$this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId);
$this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC);
$this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC);
}
/**

View File

@ -196,8 +196,8 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(2, count($someUsers));
$this->assertEquals(2, count($otherUsers));
// +2 queries executed by slice, +4 are executed by EAGER fetching of User Address.
$this->assertEquals($queryCount + 2 + 4, $this->getCurrentQueryCount());
// +2 queries executed by slice
$this->assertEquals($queryCount + 2, $this->getCurrentQueryCount(), "Slicing two parts should only execute two additional queries.");
}
/**

View File

@ -0,0 +1,198 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../TestInit.php';
/**
* @group DDC-952
*/
class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$schemaTool = new \Doctrine\ORM\Tools\SchemaTool($this->_em);
try {
$schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Train'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainDriver'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Waggon'),
));
} catch(\Exception $e) {}
}
public function testEagerLoadOneToOneOwningSide()
{
$train = new Train();
$driver = new TrainDriver("Benjamin");
$waggon = new Waggon();
$train->setDriver($driver);
$train->addWaggon($waggon);
$this->_em->persist($train); // cascades
$this->_em->flush();
$this->_em->clear();
$sqlCount = count($this->_sqlLoggerStack->queries);
$train = $this->_em->find(get_class($train), $train->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $train->driver);
$this->assertEquals("Benjamin", $train->driver->name);
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
}
public function testEagerLoadOneToOneNullOwningSide()
{
$train = new Train();
$this->_em->persist($train); // cascades
$this->_em->flush();
$this->_em->clear();
$sqlCount = count($this->_sqlLoggerStack->queries);
$train = $this->_em->find(get_class($train), $train->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $train->driver);
$this->assertNull($train->driver);
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
}
public function testEagerLoadOneToOneInverseSide()
{
$train = new Train();
$driver = new TrainDriver("Benjamin");
$train->setDriver($driver);
$this->_em->persist($train); // cascades
$this->_em->flush();
$this->_em->clear();
$sqlCount = count($this->_sqlLoggerStack->queries);
$driver = $this->_em->find(get_class($driver), $driver->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $driver->train);
$this->assertNotNull($driver->train);
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
}
public function testEagerLoadOneToOneNullInverseSide()
{
$driver = new TrainDriver("Dagny Taggert");
$this->_em->persist($driver);
$this->_em->flush();
$this->_em->clear();
$this->assertNull($driver->train);
$sqlCount = count($this->_sqlLoggerStack->queries);
$driver = $this->_em->find(get_class($driver), $driver->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $driver->train);
$this->assertNull($driver->train);
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
}
public function testEagerLoadManyToOne()
{
$train = new Train();
$waggon = new Waggon();
$train->addWaggon($waggon);
$this->_em->persist($train); // cascades
$this->_em->flush();
$this->_em->clear();
$waggon = $this->_em->find(get_class($waggon), $waggon->id);
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $waggon->train);
$this->assertNotNull($waggon->train);
}
}
/**
* @Entity
*/
class Train
{
/**
* @id @column(type="integer") @generatedValue
* @var int
*/
public $id;
/**
* Owning side
* @OneToOne(targetEntity="TrainDriver", inversedBy="train", fetch="EAGER", cascade={"persist"})
*/
public $driver;
/**
* @oneToMany(targetEntity="Waggon", mappedBy="train", cascade={"persist"})
*/
public $waggons;
public function __construct()
{
$this->waggons = new \Doctrine\Common\Collections\ArrayCollection();
}
public function setDriver(TrainDriver $driver)
{
$this->driver = $driver;
$driver->setTrain($this);
}
public function addWaggon(Waggon $w)
{
$w->setTrain($this);
$this->waggons[] = $w;
}
}
/**
* @Entity
*/
class TrainDriver
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @column(type="string") */
public $name;
/**
* Inverse side
* @OneToOne(targetEntity="Train", mappedBy="driver", fetch="EAGER")
*/
public $train;
public function __construct($name)
{
$this->name = $name;
}
public function setTrain(Train $t)
{
$this->train = $t;
}
}
/**
* @Entity
*/
class Waggon
{
/** @id @generatedValue @column(type="integer") */
public $id;
/** @ManyToOne(targetEntity="Train", inversedBy="waggons", fetch="EAGER") */
public $train;
public function setTrain($train)
{
$this->train = $train;
}
}

View File

@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\ORM\Mapping\ClassMetadata;
require_once __DIR__ . '/../../TestInit.php';
@ -313,4 +314,34 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($result[0]->user instanceof \Doctrine\ORM\Proxy\Proxy);
$this->assertFalse($result[0]->user->__isInitialized__);
}
/**
* @group DDC-952
*/
public function testEnableFetchEagerMode()
{
for ($i = 0; $i < 10; $i++) {
$article = new CmsArticle;
$article->topic = "dr. dolittle";
$article->text = "Once upon a time ...";
$author = new CmsUser;
$author->name = "anonymous";
$author->username = "anon".$i;
$author->status = "here";
$article->user = $author;
$this->_em->persist($author);
$this->_em->persist($article);
}
$this->_em->flush();
$this->_em->clear();
$articles = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsArticle a')
->setFetchMode('Doctrine\Tests\Models\CMS\CmsArticle', 'user', ClassMetadata::FETCH_EAGER)
->getResult();
$this->assertEquals(10, count($articles));
foreach ($articles AS $article) {
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $article);
}
}
}

View File

@ -42,7 +42,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
$id = $this->createProduct();
$productProxy = $this->_factory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceProduct', array('id' => $id));
$productProxy = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct', array('id' => $id));
$this->assertEquals('Doctrine Cookbook', $productProxy->getName());
}

View File

@ -2,6 +2,8 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Mapping\ClassMetadata;
require_once __DIR__ . '/../../TestInit.php';
class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
@ -349,4 +351,20 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$ref = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyFixContract', $this->fix->getId());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $ref, "A proxy can be generated only if no subclasses exists for the requested reference.");
}
/**
* @group DDC-952
*/
public function testEagerLoadInheritanceHierachy()
{
$this->loadFullFixture();
$dql = 'SELECT f FROM Doctrine\Tests\Models\Company\CompanyFixContract f WHERE f.id = ?1';
$contract = $this->_em->createQuery($dql)
->setFetchMode('Doctrine\Tests\Models\Company\CompanyFixContract', 'salesPerson', ClassMetadata::FETCH_EAGER)
->setParameter(1, $this->fix->getId())
->getSingleResult();
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $contract->getSalesPerson());
}
}

View File

@ -33,12 +33,14 @@ class StandardEntityPersisterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($customer);
$this->_em->flush();
$this->_em->clear();
$cardId = $cart->getId();
unset($cart);
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart');
$persister = $this->_em->getUnitOfWork()->getEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceCart');
$newCart = new ECommerceCart();
$this->_em->getUnitOfWork()->registerManaged($newCart, array('id' => $cardId), array());
$persister->load(array('customer_id' => $customer->getId()), $newCart, $class->associationMappings['customer']);
$this->assertEquals('Credit card', $newCart->getPayment());
}

View File

@ -0,0 +1,37 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1050
*/
class DDC1050Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
$this->markTestSkipped('performance skipped');
$this->useModelSet('cms');
parent::setUp();
}
public function testPerformance()
{
for ($i = 2; $i < 10000; ++$i) {
$user = new \Doctrine\Tests\Models\CMS\CmsUser();
$user->status = 'developer';
$user->username = 'jwage'+$i;
$user->name = 'Jonathan';
$this->_em->persist($user);
}
$this->_em->flush();
$this->_em->clear();
$s = microtime(true);
$users = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')->findAll();
$e = microtime(true);
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
}
}

View File

@ -21,6 +21,11 @@ class DDC633Test extends \Doctrine\Tests\OrmFunctionalTestCase
}
}
/**
* @group DDC-633
* @group DDC-952
* @group DDC-914
*/
public function testOneToOneEager()
{
$app = new DDC633Appointment();
@ -35,7 +40,35 @@ class DDC633Test extends \Doctrine\Tests\OrmFunctionalTestCase
$eagerAppointment = $this->_em->find(__NAMESPACE__ . '\DDC633Appointment', $app->id);
$this->assertNotType('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient);
// Eager loading of one to one leads to fetch-join
$this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient);
$this->assertTrue($this->_em->contains($eagerAppointment->patient));
}
/**
* @group DDC-633
* @group DDC-952
*/
public function testDQLDeferredEagerLoad()
{
for ($i = 0; $i < 10; $i++) {
$app = new DDC633Appointment();
$pat = new DDC633Patient();
$app->patient = $pat;
$pat->appointment = $app;
$this->_em->persist($app);
$this->_em->persist($pat);
}
$this->_em->flush();
$this->_em->clear();
$appointments = $this->_em->createQuery("SELECT a FROM " . __NAMESPACE__ . "\DDC633Appointment a")->getResult();
foreach ($appointments AS $eagerAppointment) {
$this->assertType('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient);
$this->assertTrue($eagerAppointment->patient->__isInitialized__, "Proxy should already be initialized due to eager loading!");
}
}
}

View File

@ -119,7 +119,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase
$dateTimeDb = $this->_em->find('Doctrine\Tests\Models\Generic\DateTimeModel', $dateTime->id);
$this->assertInstanceOf('DateTime', $dateTime->datetime);
$this->assertInstanceOf('DateTime', $dateTimeDb->datetime);
$this->assertEquals('2009-10-02 20:10:52', $dateTimeDb->datetime->format('Y-m-d H:i:s'));
}

View File

@ -0,0 +1,63 @@
<?php
namespace Doctrine\Tests\ORM\Performance;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Query;
require_once __DIR__ . '/../../TestInit.php';
class InheritancePersisterPerformanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('company');
parent::setUp();
}
public function testCompanyContract()
{
$person = new \Doctrine\Tests\Models\Company\CompanyEmployee();
$person->setName('Poor Sales Guy');
$person->setDepartment('Sales');
$person->setSalary(100);
$this->_em->persist($person);
for ($i = 0; $i < 33; $i++) {
$fix = new \Doctrine\Tests\Models\Company\CompanyFixContract();
$fix->setFixPrice(1000);
$fix->setSalesPerson($person);
$fix->markCompleted();
$this->_em->persist($fix);
$flex = new \Doctrine\Tests\Models\Company\CompanyFlexContract();
$flex->setSalesPerson($person);
$flex->setHoursWorked(100);
$flex->setPricePerHour(100);
$flex->markCompleted();
$this->_em->persist($flex);
$ultra = new \Doctrine\Tests\Models\Company\CompanyFlexUltraContract();
$ultra->setSalesPerson($person);
$ultra->setHoursWorked(150);
$ultra->setPricePerHour(150);
$ultra->setMaxPrice(7000);
$this->_em->persist($ultra);
}
$this->_em->flush();
$this->_em->clear();
$start = microtime(true);
$contracts = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyContract')->findAll();
echo "99 CompanyContract: " . number_format(microtime(true) - $start, 6) . "\n";
$this->assertEquals(99, count($contracts));
$this->_em->clear();
$start = microtime(true);
$contracts = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyContract')->findAll();
echo "99 CompanyContract: " . number_format(microtime(true) - $start, 6) . "\n";
$this->assertEquals(99, count($contracts));
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Doctrine\Tests\ORM\Performance;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Query;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
require_once __DIR__ . '/../../TestInit.php';
class PersisterPerformanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testFindCmsArticle()
{
$author = new CmsUser();
$author->name = "beberlei";
$author->status = "active";
$author->username = "beberlei";
$this->_em->persist($author);
$ids = array();
for ($i = 0; $i < 100; $i++) {
$article = new CmsArticle();
$article->text = "foo";
$article->topic = "bar";
$article->user = $author;
$this->_em->persist($article);
$ids[] = $article;
}
$this->_em->flush();
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsArticle')->findAll();
echo "100 CmsArticle findAll(): " . number_format(microtime(true) - $start, 6) . "\n";
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsArticle')->findAll();
echo "100 CmsArticle findAll(): " . number_format(microtime(true) - $start, 6) . "\n";
$this->_em->clear();
$start = microtime(true);
for ($i = 0; $i < 100; $i++) {
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsArticle')->find($ids[$i]->id);
}
echo "100 CmsArticle find(): " . number_format(microtime(true) - $start, 6) . "\n";
$this->_em->clear();
$start = microtime(true);
for ($i = 0; $i < 100; $i++) {
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsArticle')->find($ids[$i]->id);
}
echo "100 CmsArticle find(): " . number_format(microtime(true) - $start, 6) . "\n";
}
public function testFindCmsGroup()
{
for ($i = 0; $i < 100; $i++) {
$group = new CmsGroup();
$group->name = "foo" . $i;
$this->_em->persist($group);
}
$this->_em->flush();
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsGroup')->findAll();
echo "100 CmsGroup: " . number_format(microtime(true) - $start, 6) . "\n";
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsGroup')->findAll();
echo "100 CmsGroup: " . number_format(microtime(true) - $start, 6) . "\n";
}
public function testFindCmsUser()
{
for ($i = 0; $i < 100; $i++) {
$user = new CmsUser();
$user->name = "beberlei";
$user->status = "active";
$user->username = "beberlei".$i;
$this->_em->persist($user);
}
$this->_em->flush();
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')->findAll();
echo "100 CmsUser: " . number_format(microtime(true) - $start, 6) . "\n";
$this->_em->clear();
$start = microtime(true);
$articles = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')->findAll();
echo "100 CmsUser: " . number_format(microtime(true) - $start, 6) . "\n";
}
}