1
0
mirror of synced 2025-01-30 03:51:43 +03:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Alexander 2011-11-23 22:31:17 +01:00
commit 68027e1b5f
83 changed files with 5357 additions and 1968 deletions

3
.gitignore vendored
View File

@ -7,3 +7,6 @@ download/
lib/api/ lib/api/
lib/Doctrine/Common lib/Doctrine/Common
lib/Doctrine/DBAL lib/Doctrine/DBAL
/.settings/
.buildpath
.project

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: php
php:
- 5.3
- 5.4
env:
- DB=mysql
- DB=pgsql
- DB=sqlite
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- git submodule update --init
script: phpunit --configuration tests/travis/$DB.travis.xml

View File

@ -1,14 +1,18 @@
# Doctrine 2 ORM # Doctrine 2 ORM
Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2)
2.1.x: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication. without requiring unnecessary code duplication.
More resources: ## More resources:
* [Website](http://www.doctrine-project.org) * [Website](http://www.doctrine-project.org)
* [Documentation](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en) * [Documentation](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en)
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC) * [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
* [Downloads](http://github.com/doctrine/doctrine2/downloads) * [Downloads](http://github.com/doctrine/doctrine2/downloads)

View File

@ -199,6 +199,7 @@ class EntityManager implements ObjectManager
* the transaction is rolled back, the EntityManager closed and the exception re-thrown. * the transaction is rolled back, the EntityManager closed and the exception re-thrown.
* *
* @param Closure $func The function to execute transactionally. * @param Closure $func The function to execute transactionally.
* @return mixed Returns the non-empty value returned from the closure or true instead
*/ */
public function transactional(Closure $func) public function transactional(Closure $func)
{ {

View File

@ -225,6 +225,14 @@ class EntityRepository implements ObjectRepository
return $this->_entityName; return $this->_entityName;
} }
/**
* @return string
*/
public function getClassName()
{
return $this->getEntityName();
}
/** /**
* @return EntityManager * @return EntityManager
*/ */

View File

@ -0,0 +1,53 @@
<?php
/*
* $Id$
*
* 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\Event;
/**
* Provides event arguments for the preFlush event.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class PreFlushEventArgs extends \Doctrine\Common\EventArgs
{
/**
* @var EntityManager
*/
private $_em;
public function __construct($em)
{
$this->_em = $em;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
}

View File

@ -109,6 +109,13 @@ final class Events
*/ */
const loadClassMetadata = 'loadClassMetadata'; const loadClassMetadata = 'loadClassMetadata';
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entites have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
const preFlush = 'preFlush';
/** /**
* The onFlush event occurs when the EntityManager#flush() operation is invoked, * The onFlush event occurs when the EntityManager#flush() operation is invoked,
* after any changes to managed entities have been determined but before any * after any changes to managed entities have been determined but before any

View File

@ -46,7 +46,7 @@ class IdentityGenerator extends AbstractIdGenerator
*/ */
public function generate(EntityManager $em, $entity) public function generate(EntityManager $em, $entity)
{ {
return $em->getConnection()->lastInsertId($this->_seqName); return (int)$em->getConnection()->lastInsertId($this->_seqName);
} }
/** /**

View File

@ -61,7 +61,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
$conn = $em->getConnection(); $conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$this->_nextValue = $conn->fetchColumn($sql); $this->_nextValue = (int)$conn->fetchColumn($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize; $this->_maxValue = $this->_nextValue + $this->_allocationSize;
} }

View File

@ -195,28 +195,38 @@ abstract class AbstractHydrator
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; // NOTE: Most of the times it's a field mapping, so keep it first!!!
$cache[$key]['isScalar'] = true; case (isset($this->_rsm->fieldMappings[$key])):
} else if (isset($this->_rsm->fieldMappings[$key])) { $fieldName = $this->_rsm->fieldMappings[$key];
$fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName; $cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { break;
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. case (isset($this->_rsm->scalarMappings[$key])):
continue; $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
} else { $cache[$key]['isScalar'] = true;
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). break;
$fieldName = $this->_rsm->metaMappings[$key];
$cache[$key]['isMetaColumn'] = true; case (isset($this->_rsm->metaMappings[$key])):
$cache[$key]['fieldName'] = $fieldName; // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $fieldName = $this->_rsm->metaMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]); $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]);
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
@ -233,7 +243,7 @@ abstract class AbstractHydrator
} }
if (isset($cache[$key]['isMetaColumn'])) { if (isset($cache[$key]['isMetaColumn'])) {
if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
} }
@ -277,36 +287,51 @@ abstract class AbstractHydrator
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!!
$cache[$key]['isScalar'] = true; case (isset($this->_rsm->scalarMappings[$key])):
} else if (isset($this->_rsm->fieldMappings[$key])) { $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$fieldName = $this->_rsm->fieldMappings[$key]; $cache[$key]['isScalar'] = true;
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); break;
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); case (isset($this->_rsm->fieldMappings[$key])):
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $fieldName = $this->_rsm->fieldMappings[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. $cache[$key]['fieldName'] = $fieldName;
continue; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
} else { $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). break;
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; case (isset($this->_rsm->metaMappings[$key])):
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
$fieldName = $cache[$key]['fieldName']; $fieldName = $cache[$key]['fieldName'];
if (isset($cache[$key]['isScalar'])) { switch (true) {
$rowData[$fieldName] = $value; case (isset($cache[$key]['isScalar'])):
} else if (isset($cache[$key]['isMetaColumn'])) { $rowData[$fieldName] = $value;
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; break;
} else {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type'] case (isset($cache[$key]['isMetaColumn'])):
->convertToPHPValue($value, $this->_platform); $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
break;
default:
$value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
} }
} }
@ -319,6 +344,8 @@ abstract class AbstractHydrator
* @param Doctrine\ORM\Mapping\ClassMetadata $class * @param Doctrine\ORM\Mapping\ClassMetadata $class
* @param object $entity * @param object $entity
* @param array $data * @param array $data
*
* @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow
*/ */
protected function registerManaged(ClassMetadata $class, $entity, array $data) protected function registerManaged(ClassMetadata $class, $entity, array $data)
{ {

View File

@ -28,6 +28,11 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ArrayHydrator extends AbstractHydrator class ArrayHydrator extends AbstractHydrator
{ {
@ -85,6 +90,7 @@ class ArrayHydrator extends AbstractHydrator
// Extract scalar values. They're appended at the end. // Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) { if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars']; $scalars = $rowData['scalars'];
unset($rowData['scalars']); unset($rowData['scalars']);
if (empty($rowData)) { if (empty($rowData)) {
@ -100,7 +106,7 @@ class ArrayHydrator extends AbstractHydrator
// It's a joined result // It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias; $path = $parent . '.' . $dqlAlias;
// missing parent data, skipping as RIGHT JOIN hydration is not supported. // missing parent data, skipping as RIGHT JOIN hydration is not supported.
if ( ! isset($nonemptyComponents[$parent]) ) { if ( ! isset($nonemptyComponents[$parent]) ) {
@ -126,13 +132,14 @@ class ArrayHydrator extends AbstractHydrator
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false; $oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($baseElement[$relationAlias])) { if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
@ -142,15 +149,17 @@ class ArrayHydrator extends AbstractHydrator
} else { } else {
$baseElement[$relationAlias][] = $element; $baseElement[$relationAlias][] = $element;
} }
end($baseElement[$relationAlias]); end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
key($baseElement[$relationAlias]); $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
} }
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
} else { } else {
$oneToOne = true; $oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = null; $baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
@ -168,11 +177,12 @@ class ArrayHydrator extends AbstractHydrator
// It's a root result element // It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root $this->_rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
@ -185,7 +195,7 @@ class ArrayHydrator extends AbstractHydrator
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias]; $element = $rowData[$dqlAlias];
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$element = array(0 => $element); $element = array($entityKey => $element);
} }
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {

View File

@ -34,6 +34,11 @@ use PDO,
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
* *
* @internal Highly performance-sensitive code. * @internal Highly performance-sensitive code.
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ObjectHydrator extends AbstractHydrator class ObjectHydrator extends AbstractHydrator
{ {
@ -60,54 +65,58 @@ class ObjectHydrator extends AbstractHydrator
$this->_identifierMap = $this->_identifierMap =
$this->_resultPointers = $this->_resultPointers =
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
if (!isset($this->_hints['deferEagerLoad'])) {
if ( ! isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true; $this->_hints['deferEagerLoad'] = true;
} }
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = ''; $this->_idTemplate[$dqlAlias] = '';
$class = $this->_em->getClassMetadata($className);
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $class; $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
// Remember which associations are "fetch joined", so that we know where to inject // Remember which associations are "fetch joined", so that we know where to inject
// collection stubs or proxies and where not. // collection stubs or proxies and where not.
if (isset($this->_rsm->relationMap[$dqlAlias])) { if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { continue;
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); }
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]);
}
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true;
if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
continue;
}
// Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) {
$this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
continue;
}
// handle fetch-joined owning side bi-directional one-to-one associations
if ($assoc['inversedBy']) {
$class = $this->_ce[$className];
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
continue;
} }
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
$sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
if ($sourceClass->subClasses) {
foreach ($sourceClass->subClasses as $sourceSubclassName) {
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
}
}
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
// Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) {
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
} else {
if ($assoc['inversedBy']) {
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
if ($class->subClasses) {
foreach ($class->subClasses as $targetSubclassName) {
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
}
}
}
}
}
}
} }
} }
} }
@ -137,7 +146,7 @@ class ObjectHydrator extends AbstractHydrator
protected function hydrateAllData() protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->hydrateRowData($row, $cache, $result); $this->hydrateRowData($row, $cache, $result);
@ -155,35 +164,40 @@ class ObjectHydrator extends AbstractHydrator
* Initializes a related collection. * Initializes a related collection.
* *
* @param object $entity The entity to which the collection belongs. * @param object $entity The entity to which the collection belongs.
* @param ClassMetadata $class
* @param string $name The name of the field on the entity that holds the collection. * @param string $name The name of the field on the entity that holds the collection.
* @param string $parentDqlAlias Alias of the parent fetch joining this collection.
*/ */
private function _initRelatedCollection($entity, $class, $fieldName) private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName]; $relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
$value = $class->reflFields[$fieldName]->getValue($entity);
if ($value === null) { if ($value === null) {
$value = new ArrayCollection; $value = new ArrayCollection;
} }
if ( ! $value instanceof PersistentCollection) { if ( ! $value instanceof PersistentCollection) {
$value = new PersistentCollection( $value = new PersistentCollection(
$this->_em, $this->_em, $this->_ce[$relation['targetEntity']], $value
$this->_ce[$relation['targetEntity']],
$value
); );
$value->setOwner($entity, $relation); $value->setOwner($entity, $relation);
$class->reflFields[$fieldName]->setValue($entity, $value); $class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH]) || } else if (
isset($this->_hints['fetched'][$class->name][$fieldName]) && isset($this->_hints[Query::HINT_REFRESH]) ||
! $value->isInitialized()) { isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
! $value->isInitialized()
) {
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
$value->setDirty(false); $value->setDirty(false);
$value->setInitialized(true); $value->setInitialized(true);
$value->unwrap()->clear(); $value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else { } else {
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
@ -203,6 +217,7 @@ class ObjectHydrator extends AbstractHydrator
private function _getEntity(array $data, $dqlAlias) private function _getEntity(array $data, $dqlAlias)
{ {
$className = $this->_rsm->aliasMap[$dqlAlias]; $className = $this->_rsm->aliasMap[$dqlAlias];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
@ -211,14 +226,15 @@ class ObjectHydrator extends AbstractHydrator
} }
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]); unset($data[$discrColumn]);
} }
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
$class = $this->_ce[$className]; $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
} }
$this->_hints['fetchAlias'] = $dqlAlias;
return $this->_uow->createEntity($className, $data, $this->_hints); return $this->_uow->createEntity($className, $data, $this->_hints);
} }
@ -226,6 +242,7 @@ class ObjectHydrator extends AbstractHydrator
{ {
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent? // TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_ce[$className]; $class = $this->_ce[$className];
/* @var $class ClassMetadata */ /* @var $class ClassMetadata */
if ($class->isIdentifierComposite) { if ($class->isIdentifierComposite) {
$idHash = ''; $idHash = '';
@ -257,6 +274,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className); $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
return $this->_ce[$className]; return $this->_ce[$className];
} }
@ -292,7 +310,9 @@ class ObjectHydrator extends AbstractHydrator
// Extract scalar values. They're appended at the end. // Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) { if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars']; $scalars = $rowData['scalars'];
unset($rowData['scalars']); unset($rowData['scalars']);
if (empty($rowData)) { if (empty($rowData)) {
++$this->_resultCounter; ++$this->_resultCounter;
} }
@ -319,7 +339,7 @@ class ObjectHydrator extends AbstractHydrator
// Get a reference to the parent object to which the joined element belongs. // Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
$first = reset($this->_resultPointers); $first = reset($this->_resultPointers);
$parentObject = $this->_resultPointers[$parentAlias][key($first)]; $parentObject = $first[key($first)];
} else if (isset($this->_resultPointers[$parentAlias])) { } else if (isset($this->_resultPointers[$parentAlias])) {
$parentObject = $this->_resultPointers[$parentAlias]; $parentObject = $this->_resultPointers[$parentAlias];
} else { } else {
@ -341,7 +361,7 @@ class ObjectHydrator extends AbstractHydrator
if (isset($this->_initializedCollections[$collKey])) { if (isset($this->_initializedCollections[$collKey])) {
$reflFieldValue = $this->_initializedCollections[$collKey]; $reflFieldValue = $this->_initializedCollections[$collKey];
} else if ( ! isset($this->_existingCollections[$collKey])) { } else if ( ! isset($this->_existingCollections[$collKey])) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} }
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
@ -376,7 +396,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
} }
} else if ( ! $reflField->getValue($parentObject)) { } else if ( ! $reflField->getValue($parentObject)) {
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} }
} else { } else {
// PATH B: Single-valued association // PATH B: Single-valued association
@ -387,6 +407,7 @@ class ObjectHydrator extends AbstractHydrator
$reflField->setValue($parentObject, $element); $reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation['targetEntity']]; $targetClass = $this->_ce[$relation['targetEntity']];
if ($relation['isOwningSide']) { if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here? //TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional // If there is an inverse mapping on the target class its bidirectional
@ -417,11 +438,12 @@ class ObjectHydrator extends AbstractHydrator
} else { } else {
// PATH C: Its a root result element // PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias $this->_rootAliases[$dqlAlias] = true; // Mark as root alias
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
@ -434,7 +456,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$element = array(0 => $element); $element = array($entityKey => $element);
} }
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {

View File

@ -313,6 +313,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$class->containsForeignIdentifier = true; $class->containsForeignIdentifier = true;
} }
if ($parent && !empty ($parent->namedQueries)) {
$this->addInheritedNamedQueries($class, $parent);
}
$class->setParentClasses($visited); $class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) { if ($this->evm->hasListeners(Events::loadClassMetadata)) {
@ -429,6 +433,25 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
} }
} }
/**
* Adds inherited named queries to the subclass mapping.
*
* @since 2.2
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->namedQueries as $name => $query) {
if (!isset ($subClass->namedQueries[$name])) {
$subClass->addNamedQuery(array(
'name' => $query['name'],
'query' => $query['query']
));
}
}
}
/** /**
* Completes the ID generator mapping. If "auto" is specified we choose the generator * Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform. * most appropriate for the targeted database platform.

View File

@ -20,6 +20,7 @@
namespace Doctrine\ORM\Mapping; namespace Doctrine\ORM\Mapping;
use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
use ReflectionClass; use ReflectionClass;
/** /**
@ -685,7 +686,7 @@ class ClassMetadataInfo implements ClassMetadata
if ( ! isset($this->namedQueries[$queryName])) { if ( ! isset($this->namedQueries[$queryName])) {
throw MappingException::queryNotFound($this->name, $queryName); throw MappingException::queryNotFound($this->name, $queryName);
} }
return $this->namedQueries[$queryName]; return $this->namedQueries[$queryName]['dql'];
} }
/** /**
@ -746,6 +747,14 @@ class ClassMetadataInfo implements ClassMetadata
$this->isIdentifierComposite = true; $this->isIdentifierComposite = true;
} }
} }
if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
if (isset($mapping['id']) && $mapping['id'] === true) {
throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
}
$mapping['requireSQLConversion'] = true;
}
} }
/** /**
@ -783,6 +792,13 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
} }
if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 &&
isset($mapping['orphanRemoval']) &&
$mapping['orphanRemoval'] == true) {
throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
}
// Complete id mapping // Complete id mapping
if (isset($mapping['id']) && $mapping['id'] === true) { if (isset($mapping['id']) && $mapping['id'] === true) {
if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
@ -1448,8 +1464,15 @@ class ClassMetadataInfo implements ClassMetadata
if (isset($this->namedQueries[$queryMapping['name']])) { if (isset($this->namedQueries[$queryMapping['name']])) {
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
} }
$query = str_replace('__CLASS__', $this->name, $queryMapping['query']);
$this->namedQueries[$queryMapping['name']] = $query; $name = $queryMapping['name'];
$query = $queryMapping['query'];
$dql = str_replace('__CLASS__', $this->name, $query);
$this->namedQueries[$name] = array(
'name' => $name,
'query' => $query,
'dql' => $dql
);
} }
/** /**
@ -1979,4 +2002,22 @@ class ClassMetadataInfo implements ClassMetadata
{ {
return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name']; return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name'];
} }
/**
* @param string $fieldName
* @return bool
*/
public function isAssociationInverseSide($fieldName)
{
return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide'];
}
/**
* @param string $fieldName
* @return string
*/
public function getAssociationMappedByTargetField($fieldName)
{
return $this->associationMappings[$fieldName]['mappedBy'];
}
} }

View File

@ -446,6 +446,10 @@ class AnnotationDriver implements Driver
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) { if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad); $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
} }
if (isset($annotations['Doctrine\ORM\Mapping\PreFlush'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preFlush);
}
} }
} }
} }

View File

@ -387,3 +387,9 @@ final class PostRemove implements Annotation {}
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostLoad implements Annotation {} final class PostLoad implements Annotation {}
/**
* @Annotation
* @Target("METHOD")
*/
final class PreFlush implements Annotation {}

View File

@ -166,9 +166,12 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->field as $fieldMapping) { foreach ($xmlRoot->field as $fieldMapping) {
$mapping = array( $mapping = array(
'fieldName' => (string)$fieldMapping['name'], 'fieldName' => (string)$fieldMapping['name'],
'type' => (string)$fieldMapping['type']
); );
if (isset($fieldMapping['type'])) {
$mapping['type'] = (string)$fieldMapping['type'];
}
if (isset($fieldMapping['column'])) { if (isset($fieldMapping['column'])) {
$mapping['columnName'] = (string)$fieldMapping['column']; $mapping['columnName'] = (string)$fieldMapping['column'];
} }
@ -219,10 +222,13 @@ class XmlDriver extends AbstractFileDriver
$mapping = array( $mapping = array(
'id' => true, 'id' => true,
'fieldName' => (string)$idElement['name'], 'fieldName' => (string)$idElement['name']
'type' => (string)$idElement['type']
); );
if (isset($idElement['type'])) {
$mapping['type'] = (string)$idElement['type'];
}
if (isset($idElement['column'])) { if (isset($idElement['column'])) {
$mapping['columnName'] = (string)$idElement['column']; $mapping['columnName'] = (string)$idElement['column'];
} }
@ -371,10 +377,6 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade); $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
} }
if (isset($manyToOneElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement->{'orphan-removal'};
}
$metadata->mapManyToOne($mapping); $metadata->mapManyToOne($mapping);
} }
} }
@ -422,10 +424,6 @@ class XmlDriver extends AbstractFileDriver
$mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade); $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
} }
if (isset($manyToManyElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
}
if (isset($manyToManyElement->{'order-by'})) { if (isset($manyToManyElement->{'order-by'})) {
$orderBy = array(); $orderBy = array();
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} AS $orderByField) { foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} AS $orderByField) {

View File

@ -165,16 +165,15 @@ class YamlDriver extends AbstractFileDriver
continue; continue;
} }
if (!isset($idElement['type'])) {
throw MappingException::propertyTypeIsRequired($className, $name);
}
$mapping = array( $mapping = array(
'id' => true, 'id' => true,
'fieldName' => $name, 'fieldName' => $name
'type' => $idElement['type']
); );
if (isset($idElement['type'])) {
$mapping['type'] = $idElement['type'];
}
if (isset($idElement['column'])) { if (isset($idElement['column'])) {
$mapping['columnName'] = $idElement['column']; $mapping['columnName'] = $idElement['column'];
} }
@ -201,19 +200,21 @@ class YamlDriver extends AbstractFileDriver
// Evaluate fields // Evaluate fields
if (isset($element['fields'])) { if (isset($element['fields'])) {
foreach ($element['fields'] as $name => $fieldMapping) { foreach ($element['fields'] as $name => $fieldMapping) {
if (!isset($fieldMapping['type'])) {
throw MappingException::propertyTypeIsRequired($className, $name); $mapping = array(
'fieldName' => $name
);
if (isset($fieldMapping['type'])) {
$e = explode('(', $fieldMapping['type']);
$fieldMapping['type'] = $e[0];
$mapping['type'] = $fieldMapping['type'];
if (isset($e[1])) {
$fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
}
} }
$e = explode('(', $fieldMapping['type']);
$fieldMapping['type'] = $e[0];
if (isset($e[1])) {
$fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
}
$mapping = array(
'fieldName' => $name,
'type' => $fieldMapping['type']
);
if (isset($fieldMapping['id'])) { if (isset($fieldMapping['id'])) {
$mapping['id'] = true; $mapping['id'] = true;
if (isset($fieldMapping['generator']['strategy'])) { if (isset($fieldMapping['generator']['strategy'])) {
@ -378,10 +379,6 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToOneElement['cascade']; $mapping['cascade'] = $manyToOneElement['cascade'];
} }
if (isset($manyToOneElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
}
$metadata->mapManyToOne($mapping); $metadata->mapManyToOne($mapping);
} }
} }
@ -437,10 +434,6 @@ class YamlDriver extends AbstractFileDriver
$mapping['cascade'] = $manyToManyElement['cascade']; $mapping['cascade'] = $manyToManyElement['cascade'];
} }
if (isset($manyToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
}
if (isset($manyToManyElement['orderBy'])) { if (isset($manyToManyElement['orderBy'])) {
$mapping['orderBy'] = $manyToManyElement['orderBy']; $mapping['orderBy'] = $manyToManyElement['orderBy'];
} }

View File

@ -226,6 +226,11 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported."); return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
} }
public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type)
{
return new self("It is not possible to set id field '$fieldName' to type '$type' in entity class '$className'. The type '$type' requires conversion SQL which is not allowed for identifiers.");
}
/** /**
* @param string $className * @param string $className
* @param string $columnName * @param string $columnName
@ -270,6 +275,12 @@ class MappingException extends \Doctrine\ORM\ORMException
"part of the identifier in '$className#$field'."); "part of the identifier in '$className#$field'.");
} }
public static function illegalOrphanRemoval($className, $field)
{
return new self("Orphan removal is only allowed on one-to-one and one-to-many ".
"associations, but " . $className."#" .$field . " is not.");
}
public static function illegalInverseIdentifierAssocation($className, $field) public static function illegalInverseIdentifierAssocation($className, $field)
{ {
return new self("An inverse association is not allowed to be identifier in '$className#$field'."); return new self("An inverse association is not allowed to be identifier in '$className#$field'.");

View File

@ -59,6 +59,15 @@ class ORMException extends Exception
return new self("Unrecognized field: $field"); return new self("Unrecognized field: $field");
} }
/**
* @param string $className
* @param string $field
*/
public static function invalidOrientation($className, $field)
{
return new self("Invalid order by orientation specified for " . $className . "#" . $field);
}
public static function invalidFlushMode($mode) public static function invalidFlushMode($mode)
{ {
return new self("'$mode' is an invalid flush mode."); return new self("'$mode' is an invalid flush mode.");

View File

@ -567,6 +567,9 @@ final class PersistentCollection implements Collection
return; return;
} }
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
// hence for event listeners we need the objects in memory.
$this->initialize();
foreach ($this->coll as $element) { foreach ($this->coll as $element) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element); $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
} }

View File

@ -65,6 +65,11 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
$columnAlias = $this->getSQLColumnAlias($columnName); $columnAlias = $this->getSQLColumnAlias($columnName);
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($field));
$sql = $type->convertToPHPValueSQL($sql, $this->_platform);
}
return $sql . ' AS ' . $columnAlias; return $sql . ' AS ' . $columnAlias;
} }

View File

@ -338,10 +338,19 @@ class BasicEntityPersister
$set = $params = $types = array(); $set = $params = $types = array();
foreach ($updateData as $columnName => $value) { foreach ($updateData as $columnName => $value) {
$set[] = (isset($this->_class->fieldNames[$columnName])) $column = $columnName;
? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?' $placeholder = '?';
: $columnName . ' = ?';
if (isset($this->_class->fieldNames[$columnName])) {
$column = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform);
if (isset($this->_class->fieldMappings[$this->_class->fieldNames[$columnName]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$columnName]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
}
}
$set[] = $column . ' = ' . $placeholder;
$params[] = $value; $params[] = $value;
$types[] = $this->_columnTypes[$columnName]; $types[] = $this->_columnTypes[$columnName];
} }
@ -628,13 +637,7 @@ class BasicEntityPersister
$hints = array(); $hints = array();
if ($isInverseSingleValued) { if ($isInverseSingleValued) {
$hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; $hints['fetched']["r"][$assoc['inversedBy']] = true;
if ($targetClass->subClasses) {
foreach ($targetClass->subClasses as $targetSubclassName) {
$hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true;
}
}
} }
/* cascade read-only status /* cascade read-only status
@ -912,7 +915,6 @@ class BasicEntityPersister
* @param array $orderBy * @param array $orderBy
* @param string $baseTableAlias * @param string $baseTableAlias
* @return string * @return string
* @todo Rename: _getOrderBySQL
*/ */
protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) protected final function _getOrderBySQL(array $orderBy, $baseTableAlias)
{ {
@ -923,6 +925,11 @@ class BasicEntityPersister
throw ORMException::unrecognizedField($fieldName); throw ORMException::unrecognizedField($fieldName);
} }
$orientation = strtoupper(trim($orientation));
if ($orientation != 'ASC' && $orientation != 'DESC') {
throw ORMException::invalidOrientation($this->_class->name, $fieldName);
}
$tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ?
$this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited']) $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited'])
: $baseTableAlias; : $baseTableAlias;
@ -1123,7 +1130,19 @@ class BasicEntityPersister
); );
} else { } else {
$columns = array_unique($columns); $columns = array_unique($columns);
$values = array_fill(0, count($columns), '?');
$values = array();
foreach ($columns AS $column) {
$placeholder = '?';
if (isset($this->_columnTypes[$column]) &&
isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) {
$type = Type::getType($this->_columnTypes[$column]);
$placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
}
$values[] = $placeholder;
}
$insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform)
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
@ -1162,6 +1181,7 @@ class BasicEntityPersister
} }
} else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) { } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) {
$columns[] = $this->_class->getQuotedColumnName($name, $this->_platform); $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
$this->_columnTypes[$name] = $this->_class->fieldMappings[$name]['type'];
} }
} }
@ -1184,6 +1204,11 @@ class BasicEntityPersister
$this->_rsm->addFieldResult($alias, $columnAlias, $field); $this->_rsm->addFieldResult($alias, $columnAlias, $field);
if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($field));
$sql = $type->convertToPHPValueSQL($sql, $this->_platform);
}
return $sql . ' AS ' . $columnAlias; return $sql . ' AS ' . $columnAlias;
} }
@ -1266,12 +1291,19 @@ class BasicEntityPersister
foreach ($criteria as $field => $value) { foreach ($criteria as $field => $value) {
$conditionSql .= $conditionSql ? ' AND ' : ''; $conditionSql .= $conditionSql ? ' AND ' : '';
$placeholder = '?';
if (isset($this->_class->columnNames[$field])) { if (isset($this->_class->columnNames[$field])) {
$className = (isset($this->_class->fieldMappings[$field]['inherited'])) $className = (isset($this->_class->fieldMappings[$field]['inherited']))
? $this->_class->fieldMappings[$field]['inherited'] ? $this->_class->fieldMappings[$field]['inherited']
: $this->_class->name; : $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($this->_class->getTypeOfField($field));
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
}
} else if (isset($this->_class->associationMappings[$field])) { } else if (isset($this->_class->associationMappings[$field])) {
if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
@ -1292,7 +1324,7 @@ class BasicEntityPersister
throw ORMException::unrecognizedField($field); throw ORMException::unrecognizedField($field);
} }
$conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder);
} }
return $conditionSql; return $conditionSql;
} }

View File

@ -165,11 +165,13 @@ class ProxyFactory
{ {
$methods = ''; $methods = '';
$methodNames = array();
foreach ($class->reflClass->getMethods() as $method) { foreach ($class->reflClass->getMethods() as $method) {
/* @var $method ReflectionMethod */ /* @var $method ReflectionMethod */
if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone"))) { if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
continue; continue;
} }
$methodNames[$method->getName()] = true;
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
$methods .= "\n" . ' public function '; $methods .= "\n" . ' public function ';

View File

@ -282,9 +282,16 @@ final class Query extends AbstractQuery
} }
} }
if (count($sqlParams) != count($types)) {
throw QueryException::parameterTypeMissmatch();
}
if ($sqlParams) { if ($sqlParams) {
ksort($sqlParams); ksort($sqlParams);
$sqlParams = array_values($sqlParams); $sqlParams = array_values($sqlParams);
ksort($types);
$types = array_values($types);
} }
return array($sqlParams, $types); return array($sqlParams, $types);

View File

@ -59,7 +59,7 @@ class Composite extends Base
} }
// Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
if (mb_stripos($queryPart, ' OR ') !== false || mb_stripos($queryPart, ' AND ') !== false) { if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) {
return $this->_preSeparator . $queryPart . $this->_postSeparator; return $this->_preSeparator . $queryPart . $this->_postSeparator;
} }

File diff suppressed because it is too large Load Diff

View File

@ -77,6 +77,11 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self("Invalid parameter: token ".$key." is not defined in the query."); return new self("Invalid parameter: token ".$key." is not defined in the query.");
} }
public static function parameterTypeMissmatch()
{
return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal.");
}
public static function invalidPathExpression($pathExpr) public static function invalidPathExpression($pathExpr)
{ {
return new self( return new self(

View File

@ -36,87 +36,79 @@ namespace Doctrine\ORM\Query;
class ResultSetMapping class ResultSetMapping
{ {
/** /**
* Whether the result is mixed (contains scalar values together with field values).
*
* @ignore * @ignore
* @var boolean * @var boolean Whether the result is mixed (contains scalar values together with field values).
*/ */
public $isMixed = false; public $isMixed = false;
/** /**
* Maps alias names to class names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to class names.
*/ */
public $aliasMap = array(); public $aliasMap = array();
/** /**
* Maps alias names to related association field names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to related association field names.
*/ */
public $relationMap = array(); public $relationMap = array();
/** /**
* Maps alias names to parent alias names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to parent alias names.
*/ */
public $parentAliasMap = array(); public $parentAliasMap = array();
/** /**
* Maps column names in the result set to field names for each class.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to field names for each class.
*/ */
public $fieldMappings = array(); public $fieldMappings = array();
/** /**
* Maps column names in the result set to the alias/field name to use in the mapped result.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias/field name to use in the mapped result.
*/ */
public $scalarMappings = array(); public $scalarMappings = array();
/** /**
* Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*
* @ignore * @ignore
* @var array * @var array Maps entities in the result set to the alias name to use in the mapped result.
*/
public $entityMappings = array();
/**
* @ignore
* @var array Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*/ */
public $metaMappings = array(); public $metaMappings = array();
/** /**
* Maps column names in the result set to the alias they belong to.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias they belong to.
*/ */
public $columnOwnerMap = array(); public $columnOwnerMap = array();
/** /**
* List of columns in the result set that are used as discriminator columns.
*
* @ignore * @ignore
* @var array * @var array List of columns in the result set that are used as discriminator columns.
*/ */
public $discriminatorColumns = array(); public $discriminatorColumns = array();
/** /**
* Maps alias names to field names that should be used for indexing.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to field names that should be used for indexing.
*/ */
public $indexByMap = array(); public $indexByMap = array();
/** /**
* Map from column names to class names that declare the field the column is mapped to.
*
* @ignore * @ignore
* @var array * @var array Map from column names to class names that declare the field the column is mapped to.
*/ */
public $declaringClasses = array(); public $declaringClasses = array();
/** /**
* This is necessary to hydrate derivate foreign keys correctly. * @var array This is necessary to hydrate derivate foreign keys correctly.
*
* @var array
*/ */
public $isIdentifierColumn = array(); public $isIdentifierColumn = array();
@ -126,11 +118,19 @@ class ResultSetMapping
* @param string $class The class name of the entity. * @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity * @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping. * results or joined entity results within this ResultSetMapping.
* @param string $resultAlias The result alias with which the entity result should be
* placed in the result structure.
*
* @todo Rename: addRootEntity * @todo Rename: addRootEntity
*/ */
public function addEntityResult($class, $alias) public function addEntityResult($class, $alias, $resultAlias = null)
{ {
$this->aliasMap[$alias] = $class; $this->aliasMap[$alias] = $class;
$this->entityMappings[$alias] = $resultAlias;
if ($resultAlias !== null) {
$this->isMixed = true;
}
} }
/** /**
@ -141,6 +141,7 @@ class ResultSetMapping
* @param string $alias The alias of the entity result or joined entity result the discriminator * @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for. * column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set. * @param string $discrColumn The name of the discriminator column in the SQL result set.
*
* @todo Rename: addDiscriminatorColumn * @todo Rename: addDiscriminatorColumn
*/ */
public function setDiscriminatorColumn($alias, $discrColumn) public function setDiscriminatorColumn($alias, $discrColumn)
@ -158,20 +159,27 @@ class ResultSetMapping
public function addIndexBy($alias, $fieldName) public function addIndexBy($alias, $fieldName)
{ {
$found = false; $found = false;
foreach ($this->fieldMappings AS $columnName => $columnFieldName) { foreach ($this->fieldMappings AS $columnName => $columnFieldName) {
if ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] == $alias) { if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
$this->addIndexByColumn($alias, $columnName);
$found = true; $this->addIndexByColumn($alias, $columnName);
break; $found = true;
}
break;
} }
/* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
if (!$found) { if ( ! $found) {
throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . $message = sprintf(
$fieldName . " without calling addFieldResult() for them before."); 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
$alias,
$fieldName
);
throw new \LogicException($message);
} }
*/ */
} }
/** /**
@ -244,6 +252,7 @@ class ResultSetMapping
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
// field name => class name of declaring class // field name => class name of declaring class
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
if ( ! $this->isMixed && $this->scalarMappings) { if ( ! $this->isMixed && $this->scalarMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -260,9 +269,9 @@ class ResultSetMapping
*/ */
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
{ {
$this->aliasMap[$alias] = $class; $this->aliasMap[$alias] = $class;
$this->parentAliasMap[$alias] = $parentAlias; $this->parentAliasMap[$alias] = $parentAlias;
$this->relationMap[$alias] = $relation; $this->relationMap[$alias] = $relation;
} }
/** /**
@ -275,6 +284,7 @@ class ResultSetMapping
public function addScalarResult($columnName, $alias) public function addScalarResult($columnName, $alias)
{ {
$this->scalarMappings[$columnName] = $alias; $this->scalarMappings[$columnName] = $alias;
if ( ! $this->isMixed && $this->fieldMappings) { if ( ! $this->isMixed && $this->fieldMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -434,6 +444,7 @@ class ResultSetMapping
{ {
$this->metaMappings[$columnName] = $fieldName; $this->metaMappings[$columnName] = $fieldName;
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
if ($isIdentifierColumn) { if ($isIdentifierColumn) {
$this->isIdentifierColumn[$alias][$columnName] = true; $this->isIdentifierColumn[$alias][$columnName] = true;
} }

View File

@ -20,17 +20,20 @@
namespace Doctrine\ORM\Query; namespace Doctrine\ORM\Query;
use Doctrine\DBAL\LockMode, use Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Query, Doctrine\ORM\Query,
Doctrine\ORM\Query\QueryException; Doctrine\ORM\Query\QueryException,
Doctrine\ORM\Mapping\ClassMetadataInfo;
/** /**
* The SqlWalker is a TreeWalker that walks over a DQL AST and constructs * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
* the corresponding SQL. * the corresponding SQL.
* *
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0 * @since 2.0
* @todo Rename: SQLWalker * @todo Rename: SQLWalker
*/ */
class SqlWalker implements TreeWalker class SqlWalker implements TreeWalker
@ -295,15 +298,16 @@ class SqlWalker implements TreeWalker
{ {
$sqlParts = array(); $sqlParts = array();
foreach ($this->_selectedClasses AS $dqlAlias => $class) { foreach ($this->_selectedClasses AS $selectedClass) {
$qComp = $this->_queryComponents[$dqlAlias]; $dqlAlias = $selectedClass['dqlAlias'];
$qComp = $this->_queryComponents[$dqlAlias];
if ( ! isset($qComp['relation']['orderBy'])) continue; if ( ! isset($qComp['relation']['orderBy'])) continue;
foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
$columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform); $columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform);
$tableName = ($qComp['metadata']->isInheritanceTypeJoined()) $tableName = ($qComp['metadata']->isInheritanceTypeJoined())
? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) ? $this->_em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name)->getOwningTable($fieldName)
: $qComp['metadata']->getTableName(); : $qComp['metadata']->getTableName();
$sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation; $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation;
@ -382,9 +386,9 @@ class SqlWalker implements TreeWalker
break; break;
case LockMode::PESSIMISTIC_OPTIMISTIC: case LockMode::PESSIMISTIC_OPTIMISTIC:
foreach ($this->_selectedClasses AS $class) { foreach ($this->_selectedClasses AS $selectedClass) {
if ( ! $class->isVersioned) { if ( ! $class->isVersioned) {
throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name);
} }
} }
break; break;
@ -521,13 +525,18 @@ class SqlWalker implements TreeWalker
$this->_query->getHydrationMode() != Query::HYDRATE_OBJECT && $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT &&
$this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
foreach ($this->_selectedClasses as $dqlAlias => $class) { foreach ($this->_selectedClasses as $selectedClass) {
$class = $selectedClass['class'];
$dqlAlias = $selectedClass['dqlAlias'];
$resultAlias = $selectedClass['resultAlias'];
// Register as entity or joined entity result // Register as entity or joined entity result
if ($this->_queryComponents[$dqlAlias]['relation'] === null) { if ($this->_queryComponents[$dqlAlias]['relation'] === null) {
$this->_rsm->addEntityResult($class->name, $dqlAlias); $this->_rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
} else { } else {
$this->_rsm->addJoinedEntityResult( $this->_rsm->addJoinedEntityResult(
$class->name, $dqlAlias, $class->name,
$dqlAlias,
$this->_queryComponents[$dqlAlias]['parent'], $this->_queryComponents[$dqlAlias]['parent'],
$this->_queryComponents[$dqlAlias]['relation']['fieldName'] $this->_queryComponents[$dqlAlias]['relation']['fieldName']
); );
@ -994,9 +1003,16 @@ class SqlWalker implements TreeWalker
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform); $columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSQLColumnAlias($columnName); $columnAlias = $this->getSQLColumnAlias($columnName);
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $col = $sqlTableAlias . '.' . $columnName;
if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
$type = Type::getType($class->getTypeOfField($fieldName));
$col = $type->convertToPHPValueSQL($col, $this->_conn->getDatabasePlatform());
}
$sql .= $col . ' AS ' . $columnAlias;
if ( ! $hidden) { if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
@ -1031,7 +1047,7 @@ class SqlWalker implements TreeWalker
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
@ -1050,18 +1066,23 @@ class SqlWalker implements TreeWalker
$partialFieldSet = array(); $partialFieldSet = array();
} }
$queryComp = $this->_queryComponents[$dqlAlias]; $queryComp = $this->_queryComponents[$dqlAlias];
$class = $queryComp['metadata']; $class = $queryComp['metadata'];
$resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
if ( ! isset($this->_selectedClasses[$dqlAlias])) { if ( ! isset($this->_selectedClasses[$dqlAlias])) {
$this->_selectedClasses[$dqlAlias] = $class; $this->_selectedClasses[$dqlAlias] = array(
'class' => $class,
'dqlAlias' => $dqlAlias,
'resultAlias' => $resultAlias
);
} }
$beginning = true; $sqlParts = array();
// Select all fields from the queried class // Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) { foreach ($class->fieldMappings as $fieldName => $mapping) {
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
continue; continue;
} }
@ -1069,12 +1090,18 @@ class SqlWalker implements TreeWalker
? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName()
: $class->getTableName(); : $class->getTableName();
if ($beginning) $beginning = false; else $sql .= ', '; $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$quotedColumnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $col = $sqlTableAlias . '.' . $quotedColumnName;
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
. ' AS ' . $columnAlias; $type = Type::getType($class->getTypeOfField($fieldName));
$col = $type->convertToPHPValueSQL($col, $this->_platform);
}
$sqlParts[] = $col . ' AS '. $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
} }
@ -1085,7 +1112,7 @@ class SqlWalker implements TreeWalker
// since it requires outer joining subtables. // since it requires outer joining subtables.
if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
$sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
@ -1093,16 +1120,24 @@ class SqlWalker implements TreeWalker
continue; continue;
} }
if ($beginning) $beginning = false; else $sql .= ', '; $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$quotedColumnName = $subClass->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $col = $sqlTableAlias . '.' . $quotedColumnName;
$sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform)
. ' AS ' . $columnAlias; if (isset($subClass->fieldMappings[$fieldName]['requireSQLConversion'])) {
$type = Type::getType($subClass->getTypeOfField($fieldName));
$col = $type->convertToPHPValueSQL($col, $this->_platform);
}
$sqlParts[] = $col . ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
} }
} }
} }
$sql .= implode(', ', $sqlParts);
} }
return $sql; return $sql;
@ -1116,8 +1151,7 @@ class SqlWalker implements TreeWalker
*/ */
public function walkQuantifiedExpression($qExpr) public function walkQuantifiedExpression($qExpr)
{ {
return ' ' . strtoupper($qExpr->type) return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
. '(' . $this->walkSubselect($qExpr->subselect) . ')';
} }
/** /**
@ -1128,20 +1162,21 @@ class SqlWalker implements TreeWalker
*/ */
public function walkSubselect($subselect) public function walkSubselect($subselect)
{ {
$useAliasesBefore = $this->_useSqlTableAliases; $useAliasesBefore = $this->_useSqlTableAliases;
$rootAliasesBefore = $this->_rootAliases; $rootAliasesBefore = $this->_rootAliases;
$this->_rootAliases = array(); // reset the rootAliases for the subselect $this->_rootAliases = array(); // reset the rootAliases for the subselect
$this->_useSqlTableAliases = true; $this->_useSqlTableAliases = true;
$sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
$sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
$sql .= $this->walkWhereClause($subselect->whereClause); $sql .= $this->walkWhereClause($subselect->whereClause);
$sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
$sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
$sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
$this->_rootAliases = $rootAliasesBefore; // put the main aliases back $this->_rootAliases = $rootAliasesBefore; // put the main aliases back
$this->_useSqlTableAliases = $useAliasesBefore; $this->_useSqlTableAliases = $useAliasesBefore;
return $sql; return $sql;
@ -1162,7 +1197,7 @@ class SqlWalker implements TreeWalker
$sql = ''; $sql = '';
$rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
$dqlAlias = $rangeDecl->aliasIdentificationVariable; $dqlAlias = $rangeDecl->aliasIdentificationVariable;
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' ' $sql .= $class->getQuotedTableName($this->_platform) . ' '
@ -1290,11 +1325,18 @@ class SqlWalker implements TreeWalker
continue; continue;
} }
foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) {
$groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
$groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; $item->type = AST\PathExpression::TYPE_STATE_FIELD;
$sqlParts[] = $this->walkGroupByItem($item);
}
$sqlParts[] = $this->walkGroupByItem($groupByItem); foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) {
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
$sqlParts[] = $this->walkGroupByItem($item);
}
} }
} }

View File

@ -313,7 +313,7 @@ class QueryBuilder
* ->select('u') * ->select('u')
* ->from('User', 'u') * ->from('User', 'u')
* ->where('u.id = :user_id') * ->where('u.id = :user_id')
* ->setParameter(':user_id', 1); * ->setParameter('user_id', 1);
* </code> * </code>
* *
* @param string|integer $key The parameter position or name. * @param string|integer $key The parameter position or name.
@ -344,8 +344,8 @@ class QueryBuilder
* ->from('User', 'u') * ->from('User', 'u')
* ->where('u.id = :user_id1 OR u.id = :user_id2') * ->where('u.id = :user_id1 OR u.id = :user_id2')
* ->setParameters(array( * ->setParameters(array(
* ':user_id1' => 1, * 'user_id1' => 1,
* ':user_id2' => 2 * 'user_id2' => 2
* )); * ));
* </code> * </code>
* *
@ -539,7 +539,7 @@ class QueryBuilder
* $qb = $em->createQueryBuilder() * $qb = $em->createQueryBuilder()
* ->delete('User', 'u') * ->delete('User', 'u')
* ->where('u.id = :user_id'); * ->where('u.id = :user_id');
* ->setParameter(':user_id', 1); * ->setParameter('user_id', 1);
* </code> * </code>
* *
* @param string $delete The class/type whose instances are subject to the deletion. * @param string $delete The class/type whose instances are subject to the deletion.

View File

@ -117,7 +117,7 @@ public function <methodName>()
* @param <variableType>$<variableName> * @param <variableType>$<variableName>
* @return <entity> * @return <entity>
*/ */
public function <methodName>(<methodTypeHint>$<variableName>) public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
{ {
<spaces>$this-><fieldName> = $<variableName>; <spaces>$this-><fieldName> = $<variableName>;
<spaces>return $this; <spaces>return $this;
@ -406,7 +406,7 @@ public function <methodName>()
} }
if ($collections) { if ($collections) {
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate)); return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->_spaces, $collections), self::$_constructorMethodTemplate));
} }
return ''; return '';
@ -634,7 +634,8 @@ public function <methodName>()
foreach ($metadata->associationMappings as $associationMapping) { foreach ($metadata->associationMappings as $associationMapping) {
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $nullable = $this->_isAssociationIsNullable($associationMapping) ? 'null' : null;
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
$methods[] = $code; $methods[] = $code;
} }
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
@ -653,6 +654,22 @@ public function <methodName>()
return implode("\n\n", $methods); return implode("\n\n", $methods);
} }
private function _isAssociationIsNullable($associationMapping)
{
if (isset($associationMapping['joinColumns'])) {
$joinColumns = $associationMapping['joinColumns'];
} else {
//@todo thereis no way to retreive targetEntity metadata
$joinColumns = array();
}
foreach ($joinColumns as $joinColumn) {
if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
return false;
}
}
return true;
}
private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
{ {
if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
@ -707,7 +724,7 @@ public function <methodName>()
return implode("\n", $lines); return implode("\n", $lines);
} }
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null) private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
{ {
if ($type == "add") { if ($type == "add") {
$addMethod = explode("\\", $typeHint); $addMethod = explode("\\", $typeHint);
@ -737,6 +754,7 @@ public function <methodName>()
'<variableName>' => Inflector::camelize($fieldName), '<variableName>' => Inflector::camelize($fieldName),
'<methodName>' => $methodName, '<methodName>' => $methodName,
'<fieldName>' => $fieldName, '<fieldName>' => $fieldName,
'<variableDefault>' => ($defaultValue !== null ) ? ('='.$defaultValue) : '',
'<entity>' => $this->_getClassName($metadata) '<entity>' => $this->_getClassName($metadata)
); );
@ -805,7 +823,12 @@ public function <methodName>()
{ {
$lines = array(); $lines = array();
$lines[] = $this->_spaces . '/**'; $lines[] = $this->_spaces . '/**';
$lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity'];
if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
$lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection';
} else {
$lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity'];
}
if ($this->_generateAnnotations) { if ($this->_generateAnnotations) {
$lines[] = $this->_spaces . ' *'; $lines[] = $this->_spaces . ' *';

View File

@ -279,6 +279,9 @@ class XmlExporter extends AbstractExporter
if ($associationMapping['isCascadeDetach']) { if ($associationMapping['isCascadeDetach']) {
$cascade[] = 'cascade-detach'; $cascade[] = 'cascade-detach';
} }
if (count($cascade) === 5) {
$cascade = array('cascade-all');
}
if ($cascade) { if ($cascade) {
$cascadeXml = $associationMappingXml->addChild('cascade'); $cascadeXml = $associationMappingXml->addChild('cascade');
foreach ($cascade as $type) { foreach ($cascade as $type) {

View File

@ -147,6 +147,9 @@ class YamlExporter extends AbstractExporter
if ($associationMapping['isCascadeDetach']) { if ($associationMapping['isCascadeDetach']) {
$cascade[] = 'detach'; $cascade[] = 'detach';
} }
if (count($cascade) === 5) {
$cascade = array('all');
}
$associationMappingArray = array( $associationMappingArray = array(
'targetEntity' => $associationMapping['targetEntity'], 'targetEntity' => $associationMapping['targetEntity'],
'cascade' => $cascade, 'cascade' => $cascade,

View File

@ -173,14 +173,14 @@ class SchemaValidator
if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) {
$ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
"have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " .
"however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $assoc['relationToTargetKeyColumns'])) . "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) .
"' are missing."; "' are missing.";
} }
if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) {
$ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
"have to contain to ALL identifier columns of the source entity '". $class->name . "', " . "have to contain to ALL identifier columns of the source entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['relationToSourceKeyColumns'])) . "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) .
"' are missing."; "' are missing.";
} }
@ -200,9 +200,14 @@ class SchemaValidator
} }
if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) {
$ids = array();
foreach ($assoc['joinColumns'] AS $joinColumn) {
$ids[] = $joinColumn['name'];
}
$ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " .
"have to match to ALL identifier columns of the source entity '". $class->name . "', " . "have to match to ALL identifier columns of the source entity '". $class->name . "', " .
"however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['joinColumns'])) . "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) .
"' are missing."; "' are missing.";
} }
} }

View File

@ -24,6 +24,7 @@ use Exception, InvalidArgumentException, UnexpectedValueException,
Doctrine\Common\Collections\Collection, Doctrine\Common\Collections\Collection,
Doctrine\Common\NotifyPropertyChanged, Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener, Doctrine\Common\PropertyChangedListener,
Doctrine\Common\Persistence\ObjectManagerAware,
Doctrine\ORM\Event\LifecycleEventArgs, Doctrine\ORM\Event\LifecycleEventArgs,
Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Proxy\Proxy; Doctrine\ORM\Proxy\Proxy;
@ -259,6 +260,11 @@ class UnitOfWork implements PropertyChangedListener
*/ */
public function commit($entity = null) public function commit($entity = null)
{ {
// Raise preFlush
if ($this->evm->hasListeners(Events::preFlush)) {
$this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->em));
}
// Compute changes done since last commit. // Compute changes done since last commit.
if ($entity === null) { if ($entity === null) {
$this->computeChangeSets(); $this->computeChangeSets();
@ -485,6 +491,11 @@ class UnitOfWork implements PropertyChangedListener
$class = $this->em->getClassMetadata(get_class($entity)); $class = $this->em->getClassMetadata(get_class($entity));
} }
// Fire PreFlush lifecycle callbacks
if (isset($class->lifecycleCallbacks[Events::preFlush])) {
$class->invokeLifecycleCallbacks(Events::preFlush, $entity);
}
$actualData = array(); $actualData = array();
foreach ($class->reflFields as $name => $refProp) { foreach ($class->reflFields as $name => $refProp) {
@ -701,7 +712,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $assoc['isCascadePersist']) { if ( ! $assoc['isCascadePersist']) {
$message = "A new entity was found through the relationship '%s#%s' that was not configured " . $message = "A new entity was found through the relationship '%s#%s' that was not configured " .
' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' .
'configure cascading persist operations on tbe relationship. If you cannot find out ' . 'configure cascading persist operations on the relationship. If you cannot find out ' .
'which entity causes the problem, implement %s#__toString() to get a clue.'; 'which entity causes the problem, implement %s#__toString() to get a clue.';
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
@ -1632,7 +1643,7 @@ class UnitOfWork implements PropertyChangedListener
// If there is no ID, it is actually NEW. // If there is no ID, it is actually NEW.
if ( ! $id) { if ( ! $id) {
$managedCopy = $class->newInstance(); $managedCopy = $this->newInstance($class);
$this->persistNew($class, $managedCopy); $this->persistNew($class, $managedCopy);
} else { } else {
@ -1656,7 +1667,7 @@ class UnitOfWork implements PropertyChangedListener
throw new EntityNotFoundException; throw new EntityNotFoundException;
} }
$managedCopy = $class->newInstance(); $managedCopy = $this->newInstance($class);
$class->setIdentifierValues($managedCopy, $id); $class->setIdentifierValues($managedCopy, $id);
$this->persistNew($class, $managedCopy); $this->persistNew($class, $managedCopy);
@ -2147,6 +2158,18 @@ class UnitOfWork implements PropertyChangedListener
return isset($this->collectionsDeletions[spl_object_hash($coll)]); return isset($this->collectionsDeletions[spl_object_hash($coll)]);
} }
/**
* @param ClassMetadata $class
*/
private function newInstance($class)
{
$entity = $class->newInstance();
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class);
}
return $entity;
}
/** /**
* INTERNAL: * INTERNAL:
* Creates an entity. Used for reconstitution of persistent entities. * Creates an entity. Used for reconstitution of persistent entities.
@ -2179,6 +2202,12 @@ class UnitOfWork implements PropertyChangedListener
if (isset($class->associationMappings[$class->identifier[0]])) { if (isset($class->associationMappings[$class->identifier[0]])) {
$idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']];
} else { } else {
/*echo $className;
\Doctrine\Common\Util\Debug::dump($data);
\Doctrine\Common\Util\Debug::dump($class->identifier);
ob_end_flush();
ob_start();*/
$idHash = $data[$class->identifier[0]]; $idHash = $data[$class->identifier[0]];
} }
$id = array($class->identifier[0] => $idHash); $id = array($class->identifier[0] => $idHash);
@ -2195,13 +2224,23 @@ class UnitOfWork implements PropertyChangedListener
} }
} else { } else {
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]); $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
// If only a specific entity is set to refresh, check that it's the one
if(isset($hints[Query::HINT_REFRESH_ENTITY])) {
$overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
// inject ObjectManager into just loaded proxies.
if ($overrideLocalValues && $entity instanceof ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class);
}
}
} }
if ($overrideLocalValues) { if ($overrideLocalValues) {
$this->originalEntityData[$oid] = $data; $this->originalEntityData[$oid] = $data;
} }
} else { } else {
$entity = $class->newInstance(); $entity = $this->newInstance($class);
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$this->entityIdentifiers[$oid] = $id; $this->entityIdentifiers[$oid] = $id;
$this->entityStates[$oid] = self::STATE_MANAGED; $this->entityStates[$oid] = self::STATE_MANAGED;
@ -2232,7 +2271,7 @@ class UnitOfWork implements PropertyChangedListener
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 associations already. // Check if the association is not among the fetch-joined associations already.
if (isset($hints['fetched'][$className][$field])) { if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
continue; continue;
} }

@ -1 +1 @@
Subproject commit b2fd909b4b5476df01744c9d34c7a23723a687b6 Subproject commit 9c880cf9ae2c14102568520b5ee885b03bda93e4

View File

@ -0,0 +1,34 @@
<?php
namespace Doctrine\Tests\DbalTypes;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class NegativeToPositiveType extends Type
{
public function getName()
{
return 'negative_to_positive';
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration);
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return 'ABS(' . $sqlExpr . ')';
}
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return '-(' . $sqlExpr . ')';
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Doctrine\Tests\DbalTypes;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class UpperCaseStringType extends StringType
{
public function getName()
{
return 'upper_case_string';
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return 'UPPER(' . $sqlExpr . ')';
}
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return 'LOWER(' . $sqlExpr . ')';
}
}

View File

@ -8,6 +8,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
private $_platformMock; private $_platformMock;
private $_lastInsertId = 0; private $_lastInsertId = 0;
private $_inserts = array(); private $_inserts = array();
private $_executeUpdates = array();
public function __construct(array $params, $driver, $config = null, $eventManager = null) public function __construct(array $params, $driver, $config = null, $eventManager = null)
{ {
@ -30,11 +31,19 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
/** /**
* @override * @override
*/ */
public function insert($tableName, array $data) public function insert($tableName, array $data, array $types = array())
{ {
$this->_inserts[$tableName][] = $data; $this->_inserts[$tableName][] = $data;
} }
/**
* @override
*/
public function executeUpdate($query, array $params = array(), array $types = array())
{
$this->_executeUpdates[] = array('query' => $query, 'params' => $params, 'types' => $types);
}
/** /**
* @override * @override
*/ */
@ -84,6 +93,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
return $this->_inserts; return $this->_inserts;
} }
public function getExecuteUpdates()
{
return $this->_executeUpdates;
}
public function reset() public function reset()
{ {
$this->_inserts = array(); $this->_inserts = array();

View File

@ -87,4 +87,11 @@ class DatabasePlatformMock extends \Doctrine\DBAL\Platforms\AbstractPlatform
{ {
} }
/**
* Gets the SQL Snippet used to declare a BLOB column type.
*/
public function getBlobTypeDeclarationSQL(array $field)
{
throw DBALException::notSupported(__METHOD__);
}
} }

View File

@ -8,7 +8,7 @@ namespace Doctrine\Tests\Mocks;
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
*/ */
class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement class HydratorMockStatement implements \IteratorAggregate, \Doctrine\DBAL\Driver\Statement
{ {
private $_resultSet; private $_resultSet;
@ -98,4 +98,14 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement
public function rowCount() public function rowCount()
{ {
} }
public function getIterator()
{
return $this->_resultSet;
}
public function setFetchMode($fetchMode)
{
}
} }

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_children")
*/
class CustomTypeChild
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="upper_case_string")
*/
public $lowerCaseString = 'foo';
}

View File

@ -0,0 +1,68 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_parents")
*/
class CustomTypeParent
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="negative_to_positive", nullable=true)
*/
public $customInteger;
/**
* @OneToOne(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeChild", cascade={"persist", "remove"})
*/
public $child;
/**
* @ManyToMany(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeParent", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* @ManyToMany(targetEntity="Doctrine\Tests\Models\CustomType\CustomTypeParent", inversedBy="friendsWithMe")
* @JoinTable(
* name="customtype_parent_friends",
* joinColumns={@JoinColumn(name="customtypeparent_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_customtypeparent_id", referencedColumnName="id")}
* )
*/
private $myFriends;
public function __construct()
{
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addMyFriend(CustomTypeParent $friend)
{
$this->getMyFriends()->add($friend);
$friend->addFriendWithMe($this);
}
public function getMyFriends()
{
return $this->myFriends;
}
public function addFriendWithMe(CustomTypeParent $friend)
{
$this->getFriendsWithMe()->add($friend);
}
public function getFriendsWithMe()
{
return $this->friendsWithMe;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Models\CustomType;
/**
* @Entity
* @Table(name="customtype_uppercases")
*/
class CustomTypeUpperCase
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(type="upper_case_string")
*/
public $lowerCaseString;
}

View File

@ -0,0 +1,76 @@
<?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\Tests\Models\DDC1476;
/**
* @Entity()
*/
class DDC1476EntityWithDefaultFieldType
{
/**
* @Id
* @Column()
* @GeneratedValue("NONE")
*/
protected $id;
/** @column() */
protected $name;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
));
$metadata->mapField(array(
'fieldName' => 'name',
));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadataInfo::GENERATOR_TYPE_NONE);
}
}

View File

@ -491,5 +491,15 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository");
} }
/**
* @group DDC-1500
*/
public function testInvalidOrientation()
{
$this->setExpectedException('Doctrine\ORM\ORMException', 'Invalid order by orientation specified for Doctrine\Tests\Models\CMS\CmsUser#username');
$repo = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$repo->findBy(array('status' => 'test'), array('username' => 'INVALID'));
}
} }

View File

@ -43,6 +43,29 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('changed from preUpdate callback!', $result[0]->value); $this->assertEquals('changed from preUpdate callback!', $result[0]->value);
} }
public function testPreFlushCallbacksAreInvoked()
{
$entity = new LifecycleCallbackTestEntity;
$entity->value = 'hello';
$this->_em->persist($entity);
$this->_em->flush();
$this->assertTrue($entity->prePersistCallbackInvoked);
$this->assertTrue($entity->preFlushCallbackInvoked);
$entity->preFlushCallbackInvoked = false;
$this->_em->flush();
$this->assertTrue($entity->preFlushCallbackInvoked);
$entity->value = 'bye';
$entity->preFlushCallbackInvoked = false;
$this->_em->flush();
$this->assertTrue($entity->preFlushCallbackInvoked);
}
public function testChangesDontGetLost() public function testChangesDontGetLost()
{ {
$user = new LifecycleCallbackTestUser; $user = new LifecycleCallbackTestUser;
@ -190,6 +213,8 @@ class LifecycleCallbackTestEntity
public $postPersistCallbackInvoked = false; public $postPersistCallbackInvoked = false;
public $postLoadCallbackInvoked = false; public $postLoadCallbackInvoked = false;
public $preFlushCallbackInvoked = false;
/** /**
* @Id @Column(type="integer") * @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO") * @GeneratedValue(strategy="AUTO")
@ -233,6 +258,11 @@ class LifecycleCallbackTestEntity
public function doStuffOnPreUpdate() { public function doStuffOnPreUpdate() {
$this->value = 'changed from preUpdate callback!'; $this->value = 'changed from preUpdate callback!';
} }
/** @PreFlush */
public function doStuffOnPreFlush() {
$this->preFlushCallbackInvoked = true;
}
} }
/** /**

View File

@ -39,6 +39,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
$cleanFile = $this->_em->find(get_class($file), $file->getId()); $cleanFile = $this->_em->find(get_class($file), $file->getId());
$this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $cleanFile->getParent());
$this->assertEquals($directory->getId(), $cleanFile->getParent()->getId()); $this->assertEquals($directory->getId(), $cleanFile->getParent()->getId());
$this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent());
$this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId()); $this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId());

View File

@ -13,15 +13,14 @@ require_once __DIR__ . '/../../TestInit.php';
*/ */
class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
protected $userId;
protected function setUp() protected function setUp()
{ {
$this->useModelSet('cms'); $this->useModelSet('cms');
parent::setUp(); parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser; $user = new CmsUser;
$user->status = 'dev'; $user->status = 'dev';
$user->username = 'romanb'; $user->username = 'romanb';
@ -35,11 +34,13 @@ class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($user); $this->_em->persist($user);
$this->_em->flush(); $this->_em->flush();
$userId = $user->getId(); $this->userId = $user->getId();
$this->_em->clear(); $this->_em->clear();
}
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId); public function testOrphanRemoval()
{
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->_em->remove($userProxy); $this->_em->remove($userProxy);
$this->_em->flush(); $this->_em->flush();
@ -55,4 +56,20 @@ class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval'); $this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval');
} }
/**
* @group DDC-1496
*/
public function testOrphanRemovalUnitializedCollection()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$user->phonenumbers->clear();
$this->_em->flush();
$query = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval');
}
} }

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Common\Persistence\PersistentObject;
/**
* Test that Doctrine ORM correctly works with the ObjectManagerAware and PersistentObject
* classes from Common.
*
* @group DDC-1448
*/
class PersistentObjectTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\PersistentEntity'),
));
} catch (\Exception $e) {
}
PersistentObject::setObjectManager($this->_em);
}
public function testPersist()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
}
public function testFind()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->find(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertEquals('test', $entity->getName());
$entity->setName('foobar');
$this->_em->flush();
}
public function testGetReference()
{
$entity = new PersistentEntity();
$entity->setName("test");
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertEquals('test', $entity->getName());
}
public function testSetAssociation()
{
$entity = new PersistentEntity();
$entity->setName("test");
$entity->setParent($entity);
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
$this->assertSame($entity, $entity->getParent());
}
}
/**
* @Entity
*/
class PersistentEntity extends PersistentObject
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
protected $id;
/**
* @Column(type="string")
* @var string
*/
protected $name;
/**
* @ManyToOne(targetEntity="PersistentEntity")
* @var PersistentEntity
*/
protected $parent;
}

View File

@ -457,6 +457,42 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNull($query->getOneOrNullResult(Query::HYDRATE_SCALAR)); $this->assertNull($query->getOneOrNullResult(Query::HYDRATE_SCALAR));
} }
/**
* @group DBAL-171
*/
public function testParameterOrder()
{
$user1 = new CmsUser;
$user1->name = 'Benjamin';
$user1->username = 'beberlei';
$user1->status = 'developer';
$this->_em->persist($user1);
$user2 = new CmsUser;
$user2->name = 'Roman';
$user2->username = 'romanb';
$user2->status = 'developer';
$this->_em->persist($user2);
$user3 = new CmsUser;
$user3->name = 'Jonathan';
$user3->username = 'jwage';
$user3->status = 'developer';
$this->_em->persist($user3);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :a AND u.id IN (:b)");
$query->setParameters(array(
'b' => array($user1->id, $user2->id, $user3->id),
'a' => 'developer',
));
$result = $query->getResult();
$this->assertEquals(3, count($result));
}
public function testDqlWithAutoInferOfParameters() public function testDqlWithAutoInferOfParameters()
{ {
$user = new CmsUser; $user = new CmsUser;

View File

@ -180,7 +180,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$entity = $product->getShipping(); $entity = $product->getShipping();
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
$this->assertEquals($id, $entity->getId()); $this->assertEquals($id, $entity->getId());
$this->assertTrue($id === $entity->getId(), "Check that the id's are the same value, and type."); $this->assertSame($id, $entity->getId(), "Check that the id's are the same value, and type.");
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
} }

View File

@ -32,24 +32,28 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em); $tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes); $sql = $tool->getCreateSchemaSql($classes);
$sqlCount = count($sql);
$this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", $sql[0]); $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", array_shift($sql));
$this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", $sql[1]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[2]); $this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", array_shift($sql));
$this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", $sql[3]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", $sql[4]); $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 ON cms_users (email_id)", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", $sql[5]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", $sql[6]); $this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", array_shift($sql));
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", $sql[7]); $this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", array_shift($sql));
$this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", $sql[8]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", array_shift($sql));
$this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[9]); $this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", array_shift($sql));
$this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[10]); $this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[11]); $this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[12]); $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[13]); $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[14]); $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql));
$this->assertEquals(count($sql), 15); $this->assertEquals(array(), $sql, "SQL Array should be empty now.");
$this->assertEquals(17, $sqlCount, "Total of 17 queries should be executed");
} }
public function testGetCreateSchemaSql2() public function testGetCreateSchemaSql2()
@ -92,7 +96,7 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
$tool = new SchemaTool($this->_em); $tool = new SchemaTool($this->_em);
$sql = $tool->getDropSchemaSQL($classes); $sql = $tool->getDropSchemaSQL($classes);
$this->assertEquals(13, count($sql)); $this->assertEquals(14, count($sql));
$dropSequenceSQLs = 0; $dropSequenceSQLs = 0;
foreach ($sql AS $stmt) { foreach ($sql AS $stmt) {
if (strpos($stmt, "DROP SEQUENCE") === 0) { if (strpos($stmt, "DROP SEQUENCE") === 0) {

View File

@ -27,8 +27,8 @@ class DDC1151Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals("CREATE TABLE \"Group\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[4]); $this->assertEquals("CREATE TABLE \"Group\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[4]);
$this->assertEquals("CREATE SEQUENCE User_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[5]); $this->assertEquals("CREATE SEQUENCE User_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[5]);
$this->assertEquals("CREATE SEQUENCE Group_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[6]); $this->assertEquals("CREATE SEQUENCE Group_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[6]);
$this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151user_id) REFERENCES \"User\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]); $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A3259AC5AD08A FOREIGN KEY (ddc1151user_id) REFERENCES \"User\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]);
$this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]); $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A32597357E0B1 FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]);
} }
} }

View File

@ -49,17 +49,21 @@ class DDC1335Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3);
$this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3);
$this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); $foo = $result['foo@foo.com']->phones->toArray();
$this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); $bar = $result['bar@bar.com']->phones->toArray();
$this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); $foobar = $result['foobar@foobar.com']->phones->toArray();
$this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(1, $foo);
$this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(2, $foo);
$this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(3, $foo);
$this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(4, $bar);
$this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(5, $bar);
$this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(6, $bar);
$this->assertArrayHasKey(7, $foobar);
$this->assertArrayHasKey(8, $foobar);
$this->assertArrayHasKey(9, $foobar);
} }
public function testTicket() public function testTicket()

View File

@ -0,0 +1,136 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1400
*/
class DDC1400Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400Article'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400User'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400UserState'),
));
} catch (\Exception $ignored) {
}
}
public function testFailingCase()
{
$article = new DDC1400Article;
$user1 = new DDC1400User;
$user2 = new DDC1400User;
$this->_em->persist($article);
$this->_em->persist($user1);
$this->_em->persist($user2);
$this->_em->flush();
$userState1 = new DDC1400UserState;
$userState1->article = $article;
$userState1->articleId = $article->id;
$userState1->user = $user1;
$userState1->userId = $user1->id;
$userState2 = new DDC1400UserState;
$userState2->article = $article;
$userState2->articleId = $article->id;
$userState2->user = $user2;
$userState2->userId = $user2->id;
$this->_em->persist($userState1);
$this->_em->persist($userState2);
$this->_em->flush();
$this->_em->clear();
$user1 = $this->_em->getReference(__NAMESPACE__.'\DDC1400User', $user1->id);
$q = $this->_em->createQuery("SELECT a, s FROM ".__NAMESPACE__."\DDC1400Article a JOIN a.userStates s WITH s.user = :activeUser");
$q->setParameter('activeUser', $user1);
$articles = $q->getResult();
var_dump(array_keys($articles[0]->userStates->toArray()));
$this->_em->flush();
var_dump($this->_sqlLoggerStack);
}
}
/**
* @Entity
*/
class DDC1400Article
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
/**
* @OneToMany(targetEntity="DDC1400UserState", mappedBy="article", indexBy="userId", fetch="EXTRA_LAZY")
*/
public $userStates;
}
/**
* @Entity
*/
class DDC1400User
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
/**
* @OneToMany(targetEntity="DDC1400UserState", mappedBy="user", indexBy="articleId", fetch="EXTRA_LAZY")
*/
public $userStates;
}
/**
* @Entity
*/
class DDC1400UserState
{
/**
* @Id
* @ManyToOne(targetEntity="DDC1400Article", inversedBy="userStates")
*/
public $article;
/**
* @Id
* @ManyToOne(targetEntity="DDC1400User", inversedBy="userStates")
*/
public $user;
/**
* @Column(name="user_id", type="integer")
*/
public $userId;
/**
* @Column(name="article_id", type="integer")
*/
public $articleId;
}

View File

@ -0,0 +1,129 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1404
*/
class DDC1404Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1404ParentEntity'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1404ChildEntity'),
));
$this->loadFixtures();
} catch (Exception $exc) {
}
}
public function testTicket()
{
$repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1404ChildEntity');
$queryAll = $repository->createNamedQuery('all');
$queryFirst = $repository->createNamedQuery('first');
$querySecond = $repository->createNamedQuery('second');
$this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p', $queryAll->getDQL());
$this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p WHERE p.id = 1', $queryFirst->getDQL());
$this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p WHERE p.id = 2', $querySecond->getDQL());
$this->assertEquals(sizeof($queryAll->getResult()), 2);
$this->assertEquals(sizeof($queryFirst->getResult()), 1);
$this->assertEquals(sizeof($querySecond->getResult()), 1);
}
public function loadFixtures()
{
$c1 = new DDC1404ChildEntity("ChildEntity 1");
$c2 = new DDC1404ChildEntity("ChildEntity 2");
$this->_em->persist($c1);
$this->_em->persist($c2);
$this->_em->flush();
}
}
/**
* @MappedSuperclass
*
* @NamedQueries({
* @NamedQuery(name="all", query="SELECT p FROM __CLASS__ p"),
* @NamedQuery(name="first", query="SELECT p FROM __CLASS__ p WHERE p.id = 1"),
* })
*/
class DDC1404ParentEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue()
*/
protected $id;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
}
/**
* @Entity
*
* @NamedQueries({
* @NamedQuery(name="first", query="SELECT p FROM __CLASS__ p WHERE p.id = 1"),
* @NamedQuery(name="second", query="SELECT p FROM __CLASS__ p WHERE p.id = 2")
* })
*/
class DDC1404ChildEntity extends DDC1404ParentEntity
{
/**
* @column(type="string")
*/
private $name;
/**
* @param string $name
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
}

View File

@ -0,0 +1,297 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1430
*/
class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1430Order'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1430OrderProduct'),
));
$this->loadFixtures();
} catch (\Exception $exc) {
}
}
public function testOrderByFields()
{
$repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order');
$builder = $repository->createQueryBuilder('o');
$query = $builder->select('o.id, o.date, COUNT(p.id) AS p_count')
->leftJoin('o.products', 'p')
->groupBy('o.id, o.date')
->orderBy('o.id')
->getQuery();
$this->assertEquals('SELECT o.id, o.date, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
$this->assertEquals(2, sizeof($result));
$this->assertArrayHasKey('id', $result[0]);
$this->assertArrayHasKey('id', $result[1]);
$this->assertArrayHasKey('p_count', $result[0]);
$this->assertArrayHasKey('p_count', $result[1]);
$this->assertEquals(1, $result[0]['id']);
$this->assertEquals(2, $result[1]['id']);
$this->assertEquals(2, $result[0]['p_count']);
$this->assertEquals(3, $result[1]['p_count']);
}
public function testOrderByAllObjectFields()
{
$repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order');
$builder = $repository->createQueryBuilder('o');
$query = $builder->select('o, COUNT(p.id) AS p_count')
->leftJoin('o.products', 'p')
->groupBy('o.id, o.date, o.status')
->orderBy('o.id')
->getQuery();
$this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date, o.status ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
$this->assertEquals(2, sizeof($result));
$this->assertTrue($result[0][0] instanceof DDC1430Order);
$this->assertTrue($result[1][0] instanceof DDC1430Order);
$this->assertEquals($result[0][0]->getId(), 1);
$this->assertEquals($result[1][0]->getId(), 2);
$this->assertEquals($result[0]['p_count'], 2);
$this->assertEquals($result[1]['p_count'], 3);
}
public function testTicket()
{
$repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order');
$builder = $repository->createQueryBuilder('o');
$query = $builder->select('o, COUNT(p.id) AS p_count')
->leftJoin('o.products', 'p')
->groupBy('o')
->orderBy('o.id')
->getQuery();
$this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o ORDER BY o.id ASC', $query->getDQL());
$this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL());
$result = $query->getResult();
$this->assertEquals(2, sizeof($result));
$this->assertTrue($result[0][0] instanceof DDC1430Order);
$this->assertTrue($result[1][0] instanceof DDC1430Order);
$this->assertEquals($result[0][0]->getId(), 1);
$this->assertEquals($result[1][0]->getId(), 2);
$this->assertEquals($result[0]['p_count'], 2);
$this->assertEquals($result[1]['p_count'], 3);
}
public function loadFixtures()
{
$o1 = new DDC1430Order('NEW');
$o2 = new DDC1430Order('OK');
$o1->addProduct(new DDC1430OrderProduct(1.1));
$o1->addProduct(new DDC1430OrderProduct(1.2));
$o2->addProduct(new DDC1430OrderProduct(2.1));
$o2->addProduct(new DDC1430OrderProduct(2.2));
$o2->addProduct(new DDC1430OrderProduct(2.3));
$this->_em->persist($o1);
$this->_em->persist($o2);
$this->_em->flush();
}
}
/**
* @Entity
*/
class DDC1430Order
{
/**
* @Id
* @Column(name="order_id", type="integer")
* @GeneratedValue()
*/
protected $id;
/**
* @Column(name="created_at", type="datetime")
*/
private $date;
/**
* @Column(name="order_status", type="string")
*/
private $status;
/**
* @OneToMany(targetEntity="DDC1430OrderProduct", mappedBy="order", cascade={"persist", "remove"})
*
* @var \Doctrine\Common\Collections\ArrayCollection $products
*/
private $products;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
public function __construct($status)
{
$this->status = $status;
$this->date = new \DateTime();
$this->products = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* @param string $status
*/
public function setStatus($status)
{
$this->status = $status;
}
/**
* @return \Doctrine\Common\Collections\ArrayCollection
*/
public function getProducts()
{
return $this->products;
}
/**
* @param DDC1430OrderProduct $product
*/
public function addProduct(DDC1430OrderProduct $product)
{
$product->setOrder($this);
$this->products->add($product);
}
}
/**
* @Entity
*/
class DDC1430OrderProduct
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue()
*/
protected $id;
/**
* @var DDC1430Order $order
*
* @ManyToOne(targetEntity="DDC1430Order", inversedBy="products")
* @JoinColumn(name="order_id", referencedColumnName="order_id", nullable = false)
*/
private $order;
/**
* @column(type="float")
*/
private $value;
/**
* @param float $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return DDC1430Order
*/
public function getOrder()
{
return $this->order;
}
/**
* @param DDC1430Order $order
*/
public function setOrder(DDC1430Order $order)
{
$this->order = $order;
}
/**
* @return float
*/
public function getValue()
{
return $this->value;
}
/**
* @param float $value
*/
public function setValue($value)
{
$this->value = $value;
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
/**
* @group DDC-1436
*/
class DDC1436Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1436Page'),
));
} catch (\Exception $ignored) {
}
}
public function testIdentityMap()
{
// fixtures
$parent = null;
for ($i = 0; $i < 3; $i++) {
$page = new DDC1436Page();
$page->setParent($parent);
$this->_em->persist($page);
$parent = $page;
}
$this->_em->flush();
$this->_em->clear();
$id = $parent->getId();
// step 1
$page = $this->_em
->createQuery('SELECT p, parent FROM ' . __NAMESPACE__ . '\DDC1436Page p LEFT JOIN p.parent parent WHERE p.id = :id')
->setParameter('id', $id)
->getOneOrNullResult();
$this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page);
// step 2
$page = $this->_em->find(__NAMESPACE__ . '\DDC1436Page', $id);
$this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page);
$this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page->getParent());
$this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page->getParent()->getParent());
}
}
/**
* @Entity
*/
class DDC1436Page
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer", name="id")
*/
protected $id;
/**
* @ManyToOne(targetEntity="DDC1436Page")
* @JoinColumn(name="pid", referencedColumnName="id")
*/
protected $parent;
public function getId()
{
return $this->id;
}
/**
* @return DDC1436Page
*/
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1452
*/
class DDC1452Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityA'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityB'),
));
} catch (\Exception $ignored) {
}
}
public function testIssue()
{
$a1 = new DDC1452EntityA();
$a1->title = "foo";
$a2 = new DDC1452EntityA();
$a2->title = "bar";
$b = new DDC1452EntityB();
$b->entityAFrom = $a1;
$b->entityATo = $a2;
$this->_em->persist($a1);
$this->_em->persist($a2);
$this->_em->persist($b);
$this->_em->flush();
$this->_em->clear();
$dql = "SELECT a, b, ba FROM " . __NAMESPACE__ . "\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba";
$results = $this->_em->createQuery($dql)->setMaxResults(1)->getResult();
$this->assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom);
$this->assertFalse( $results[0]->entitiesB[0]->entityATo instanceof \Doctrine\ORM\Proxy\Proxy );
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $results[0]->entitiesB[0]->entityATo->getEntitiesB());
}
public function testFetchJoinOneToOneFromInverse()
{
$address = new \Doctrine\Tests\Models\CMS\CmsAddress();
$address->city = "Bonn";
$address->country = "Germany";
$address->street = "Somestreet";
$address->zip = 12345;
$user = new \Doctrine\Tests\Models\CMS\CmsUser();
$user->name = "beberlei";
$user->username = "beberlei";
$user->status = "active";
$user->address = $address;
$address->user = $user;
$this->_em->persist($address);
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$dql = "SELECT a, u FROM Doctrine\Tests\Models\CMS\CmsAddress a INNER JOIN a.user u";
$data = $this->_em->createQuery($dql)->getResult();
$this->_em->clear();
$this->assertFalse($data[0]->user instanceof \Doctrine\ORM\Proxy\Proxy);
$dql = "SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.address a";
$data = $this->_em->createQuery($dql)->getResult();
$this->assertFalse($data[0]->address instanceof \Doctrine\ORM\Proxy\Proxy);
}
}
/**
* @Entity
*/
class DDC1452EntityA
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column */
public $title;
/** @ManyToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */
public $entitiesB;
public function __construct()
{
$this->entitiesB = new ArrayCollection();
}
public function getEntitiesB()
{
return $this->entitiesB;
}
}
/**
* @Entity
*/
class DDC1452EntityB
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/**
* @ManyToOne(targetEntity="DDC1452EntityA", inversedBy="entitiesB")
*/
public $entityAFrom;
/**
* @ManyToOne(targetEntity="DDC1452EntityA")
*/
public $entityATo;
}

View File

@ -0,0 +1,131 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsGroup;
require_once __DIR__ . '/../../../TestInit.php';
class DDC1258Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\TestEntity'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\TestAdditionalEntity')
));
}
public function testIssue()
{
$testEntity = new TestEntity();
$testEntity->setValue(3);
$testEntity->setAdditional(new TestAdditionalEntity());
$this->_em->persist($testEntity);
$this->_em->flush();
$this->_em->clear();
// So here the value is 3
$this->assertEquals(3, $testEntity->getValue());
$test = $this->_em->getRepository(__NAMESPACE__ . '\TestEntity')->find(1);
// New value is set
$test->setValue(5);
// So here the value is 5
$this->assertEquals(5, $test->getValue());
// Get the additional entity
$additional = $test->getAdditional();
// Still 5..
$this->assertEquals(5, $test->getValue());
// Force the proxy to load
$additional->getBool();
// The value should still be 5
$this->assertEquals(5, $test->getValue());
}
}
/**
* @Entity
*/
class TestEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @Column(type="integer")
*/
protected $value;
/**
* @OneToOne(targetEntity="TestAdditionalEntity", inversedBy="entity", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $additional;
public function getValue()
{
return $this->value;
}
public function setValue($value)
{
$this->value = $value;
}
public function getAdditional()
{
return $this->additional;
}
public function setAdditional($additional)
{
$this->additional = $additional;
}
}
/**
* @Entity
*/
class TestAdditionalEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @OneToOne(targetEntity="TestEntity", mappedBy="additional")
*/
protected $entity;
/**
* @Column(type="boolean")
*/
protected $bool;
public function __construct()
{
$this->bool = false;
}
public function getBool()
{
return $this->bool;
}
public function setBool($bool)
{
$this->bool = $bool;
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsUser;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1461
*/
class DDC1461Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1461TwitterAccount'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1461User')
));
} catch(\Exception $e) {
}
}
public function testChangeDetectionDeferredExplicit()
{
$user = new DDC1461User;
$this->_em->persist($user);
$this->_em->flush();
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user, \Doctrine\ORM\UnitOfWork::STATE_NEW), "Entity should be managed.");
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user), "Entity should be managed.");
$acc = new DDC1461TwitterAccount;
$user->twitterAccount = $acc;
$this->_em->persist($user);
$this->_em->flush();
$user = $this->_em->find(get_class($user), $user->id);
$this->assertNotNull($user->twitterAccount);
}
}
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class DDC1461User
{
/**
* @Id
* @GeneratedValue(strategy="AUTO")
* @Column(type="integer")
*/
public $id;
/**
* @OneToOne(targetEntity="DDC1461TwitterAccount", orphanRemoval=true, fetch="EAGER", cascade = {"persist"}, inversedBy="user")
* @var TwitterAccount
*/
public $twitterAccount;
}
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class DDC1461TwitterAccount
{
/**
* @Id
* @GeneratedValue(strategy="AUTO")
* @Column(type="integer")
*/
public $id;
/**
* @OneToOne(targetEntity="DDC1461User", fetch="EAGER")
*/
public $user;
}

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CustomType\CustomTypeChild;
use Doctrine\Tests\Models\CustomType\CustomTypeParent;
use Doctrine\Tests\Models\CustomType\CustomTypeUpperCase;
use Doctrine\DBAL\Types\Type as DBALType;
require_once __DIR__ . '/../../TestInit.php';
class TypeValueSqlTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
if (DBALType::hasType('upper_case_string')) {
DBALType::overrideType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
} else {
DBALType::addType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
}
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->useModelSet('customtype');
parent::setUp();
}
public function testUpperCaseStringType()
{
$entity = new CustomTypeUpperCase();
$entity->lowerCaseString = 'foo';
$this->_em->persist($entity);
$this->_em->flush();
$id = $entity->id;
$this->_em->clear();
$entity = $this->_em->find('\Doctrine\Tests\Models\CustomType\CustomTypeUpperCase', $id);
$this->assertEquals('foo', $entity->lowerCaseString, 'Entity holds lowercase string');
$this->assertEquals('FOO', $this->_em->getConnection()->fetchColumn("select lowerCaseString from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string');
}
public function testTypeValueSqlWithAssociations()
{
$parent = new CustomTypeParent();
$parent->customInteger = -1;
$parent->child = new CustomTypeChild();
$friend1 = new CustomTypeParent();
$friend2 = new CustomTypeParent();
$parent->addMyFriend($friend1);
$parent->addMyFriend($friend2);
$this->_em->persist($parent);
$this->_em->persist($friend1);
$this->_em->persist($friend2);
$this->_em->flush();
$parentId = $parent->id;
$this->_em->clear();
$entity = $this->_em->find('Doctrine\Tests\Models\CustomType\CustomTypeParent', $parentId);
$this->assertTrue($entity->customInteger < 0, 'Fetched customInteger negative');
$this->assertEquals(1, $this->_em->getConnection()->fetchColumn("select customInteger from customtype_parents where id=".$entity->id.""), 'Database has stored customInteger positive');
$this->assertNotNull($parent->child, 'Child attached');
$this->assertCount(2, $entity->getMyFriends(), '2 friends attached');
}
public function testSelectDQL()
{
$parent = new CustomTypeParent();
$parent->customInteger = -1;
$parent->child = new CustomTypeChild();
$this->_em->persist($parent);
$this->_em->flush();
$parentId = $parent->id;
$this->_em->clear();
$query = $this->_em->createQuery("SELECT p, p.customInteger, c from Doctrine\Tests\Models\CustomType\CustomTypeParent p JOIN p.child c where p.id = " . $parentId);
$result = $query->getResult();
$this->assertEquals(1, count($result));
$this->assertInstanceOf('Doctrine\Tests\Models\CustomType\CustomTypeParent', $result[0][0]);
$this->assertEquals(-1, $result[0][0]->customInteger);
$this->assertEquals(-1, $result[0]['customInteger']);
$this->assertEquals('foo', $result[0][0]->child->lowerCaseString);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -138,6 +138,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
public function testIdentifier($class) public function testIdentifier($class)
{ {
$this->assertEquals(array('id'), $class->identifier); $this->assertEquals(array('id'), $class->identifier);
$this->assertEquals('integer', $class->fieldMappings['id']['type']);
$this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $class->generatorType, "ID-Generator is not ClassMetadata::GENERATOR_TYPE_AUTO"); $this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $class->generatorType, "ID-Generator is not ClassMetadata::GENERATOR_TYPE_AUTO");
return $class; return $class;
@ -327,6 +328,51 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
$em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")); $em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment"));
$this->assertTrue($em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")->isTrue()); $this->assertTrue($em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")->isTrue());
} }
/**
* @group DDC-1476
*/
public function testDefaultFieldType()
{
$driver = $this->_loadDriver();
$em = $this->_getTestEntityManager();
$factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory();
$em->getConfiguration()->setMetadataDriverImpl($driver);
$factory->setEntityManager($em);
$class = $factory->getMetadataFor('Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType');
$this->assertArrayHasKey('id', $class->fieldMappings);
$this->assertArrayHasKey('name', $class->fieldMappings);
$this->assertArrayHasKey('type', $class->fieldMappings['id']);
$this->assertArrayHasKey('type', $class->fieldMappings['name']);
$this->assertEquals('string', $class->fieldMappings['id']['type']);
$this->assertEquals('string', $class->fieldMappings['name']['type']);
$this->assertArrayHasKey('fieldName', $class->fieldMappings['id']);
$this->assertArrayHasKey('fieldName', $class->fieldMappings['name']);
$this->assertEquals('id', $class->fieldMappings['id']['fieldName']);
$this->assertEquals('name', $class->fieldMappings['name']['fieldName']);
$this->assertArrayHasKey('columnName', $class->fieldMappings['id']);
$this->assertArrayHasKey('columnName', $class->fieldMappings['name']);
$this->assertEquals('id', $class->fieldMappings['id']['columnName']);
$this->assertEquals('name', $class->fieldMappings['name']['columnName']);
$this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType);
}
} }
/** /**

View File

@ -54,7 +54,7 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase
$this->assertEquals('phonenumbers', $oneOneMapping['fieldName']); $this->assertEquals('phonenumbers', $oneOneMapping['fieldName']);
$this->assertEquals('Doctrine\Tests\Models\CMS\Bar', $oneOneMapping['targetEntity']); $this->assertEquals('Doctrine\Tests\Models\CMS\Bar', $oneOneMapping['targetEntity']);
$this->assertTrue($cm->isReadOnly); $this->assertTrue($cm->isReadOnly);
$this->assertEquals(array('dql' => 'foo'), $cm->namedQueries); $this->assertEquals(array('dql' => array('name'=>'dql','query'=>'foo','dql'=>'foo')), $cm->namedQueries);
} }
public function testFieldIsNullable() public function testFieldIsNullable()

View File

@ -0,0 +1,12 @@
<?php
use Doctrine\ORM\Mapping\ClassMetadataInfo;
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
));
$metadata->mapField(array(
'fieldName' => 'name'
));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType">
<id name="id">
<generator strategy="NONE"/>
</id>
<field name="name"/>
</entity>
</doctrine-mapping>

View File

@ -0,0 +1,8 @@
Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType:
type: entity
id:
id:
generator:
strategy: NONE
fields:
name:

View File

@ -0,0 +1,79 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\Persisters\BasicEntityPersister;
use Doctrine\Tests\Models\CustomType\CustomTypeParent;
use Doctrine\Tests\Models\CustomType\CustomTypeChild;
use Doctrine\Tests\Models\CustomType\CustomTypeFriend;
require_once __DIR__ . '/../../TestInit.php';
class BasicEntityPersisterTypeValueSqlTest extends \Doctrine\Tests\OrmTestCase
{
protected $_persister;
protected $_em;
protected function setUp()
{
parent::setUp();
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
if (DBALType::hasType('upper_case_string')) {
DBALType::overrideType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
} else {
DBALType::addType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
}
$this->_em = $this->_getTestEntityManager();
$this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata("Doctrine\Tests\Models\CustomType\CustomTypeParent"));
}
public function testGetInsertSQLUsesTypeValuesSQL()
{
$method = new \ReflectionMethod($this->_persister, '_getInsertSQL');
$method->setAccessible(true);
$sql = $method->invoke($this->_persister);
$this->assertEquals('INSERT INTO customtype_parents (customInteger, child_id) VALUES (ABS(?), ?)', $sql);
}
public function testUpdateUsesTypeValuesSQL()
{
$child = new CustomTypeChild();
$parent = new CustomTypeParent();
$parent->customInteger = 1;
$parent->child = $child;
$this->_em->getUnitOfWork()->registerManaged($parent, array('id' => 1), array('customInteger' => 0, 'child' => null));
$this->_em->getUnitOfWork()->registerManaged($child, array('id' => 1), array());
$this->_em->getUnitOfWork()->propertyChanged($parent, 'customInteger', 0, 1);
$this->_em->getUnitOfWork()->propertyChanged($parent, 'child', null, $child);
$this->_persister->update($parent);
$executeUpdates = $this->_em->getConnection()->getExecuteUpdates();
$this->assertEquals('UPDATE customtype_parents SET customInteger = ABS(?), child_id = ? WHERE id = ?', $executeUpdates[0]['query']);
}
public function testGetSelectConditionSQLUsesTypeValuesSQL()
{
$method = new \ReflectionMethod($this->_persister, '_getSelectConditionSQL');
$method->setAccessible(true);
$sql = $method->invoke($this->_persister, array('customInteger' => 1, 'child' => 1));
$this->assertEquals('t0.customInteger = ABS(?) AND t0.child_id = ?', $sql);
}
}

View File

@ -2,6 +2,7 @@
namespace Doctrine\Tests\ORM\Query; namespace Doctrine\Tests\ORM\Query;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -945,7 +946,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'SELECT g, count(u.id) FROM Doctrine\Tests\Models\CMS\CmsGroup g JOIN g.users u GROUP BY g', 'SELECT g, count(u.id) FROM Doctrine\Tests\Models\CMS\CmsGroup g JOIN g.users u GROUP BY g',
'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id' 'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id, c0_.name'
); );
} }
@ -1301,6 +1302,94 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)" "SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)"
); );
} }
/**
* @group DDC-1474
*/
public function testSelectWithArithmeticExpressionBeforeField()
{
$this->assertSqlGeneration(
'SELECT - e.value AS value, e.id FROM ' . __NAMESPACE__ . '\DDC1474Entity e',
'SELECT -d0_.value AS sclr0, d0_.id AS id1 FROM DDC1474Entity d0_'
);
$this->assertSqlGeneration(
'SELECT e.id, + e.value AS value FROM ' . __NAMESPACE__ . '\DDC1474Entity e',
'SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_'
);
}
/**
* @group DDC-1430
*/
public function testGroupByAllFieldsWhenObjectHasForeignKeys()
{
$this->assertSqlGeneration(
'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY c0_.id, c0_.status, c0_.username, c0_.name, c0_.email_id'
);
$this->assertSqlGeneration(
'SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmployee e GROUP BY e',
'SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name, c0_.spouse_id'
);
}
public function testCustomTypeValueSql()
{
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->assertSqlGeneration(
'SELECT p.customInteger FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p WHERE p.id = 1',
'SELECT -(c0_.customInteger) AS customInteger0 FROM customtype_parents c0_ WHERE c0_.id = 1'
);
}
public function testCustomTypeValueSqlIgnoresIdentifierColumn()
{
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->assertSqlGeneration(
'SELECT p.id FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p WHERE p.id = 1',
'SELECT c0_.id AS id0 FROM customtype_parents c0_ WHERE c0_.id = 1'
);
}
public function testCustomTypeValueSqlForAllFields()
{
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->assertSqlGeneration(
'SELECT p FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p',
'SELECT c0_.id AS id0, -(c0_.customInteger) AS customInteger1 FROM customtype_parents c0_'
);
}
public function testCustomTypeValueSqlForPartialObject()
{
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->assertSqlGeneration(
'SELECT partial p.{id, customInteger} FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p',
'SELECT c0_.id AS id0, -(c0_.customInteger) AS customInteger1 FROM customtype_parents c0_'
);
}
} }
@ -1343,3 +1432,57 @@ class DDC1384Model
*/ */
protected $aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo; protected $aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo;
} }
/**
* @Entity
*/
class DDC1474Entity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue()
*/
protected $id;
/**
* @column(type="float")
*/
private $value;
/**
* @param string $float
*/
public function __construct($float)
{
$this->value = $float;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return float
*/
public function getValue()
{
return $this->value;
}
/**
* @param float $value
*/
public function setValue($value)
{
$this->value = $value;
}
}

View File

@ -21,6 +21,8 @@
namespace Doctrine\Tests\ORM\Query; namespace Doctrine\Tests\ORM\Query;
use Doctrine\DBAL\Types\Type as DBALType;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
/** /**
@ -42,6 +44,12 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
private $_em; private $_em;
protected function setUp() { protected function setUp() {
if (DBALType::hasType('negative_to_positive')) {
DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
} else {
DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
}
$this->_em = $this->_getTestEntityManager(); $this->_em = $this->_getTestEntityManager();
} }
@ -186,4 +194,12 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"UPDATE cms_users SET status = 'inactive' WHERE (SELECT COUNT(*) FROM cms_users_groups c0_ WHERE c0_.user_id = cms_users.id) = 10" "UPDATE cms_users SET status = 'inactive' WHERE (SELECT COUNT(*) FROM cms_users_groups c0_ WHERE c0_.user_id = cms_users.id) = 10"
); );
} }
public function testCustomTypeValueSqlCompletelyIgnoredInUpdateStatements()
{
$this->assertSqlGeneration(
'UPDATE Doctrine\Tests\Models\CustomType\CustomTypeParent p SET p.customInteger = 1 WHERE p.id = 1',
'UPDATE customtype_parents SET customInteger = 1 WHERE id = 1'
);
}
} }

View File

@ -324,7 +324,33 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
{ {
$this->assertEquals('user', $class->associationMappings['address']['inversedBy']); $this->assertEquals('user', $class->associationMappings['address']['inversedBy']);
} }
/**
* @depends testExportDirectoryAndFilesAreCreated
*/
public function testCascadeAllCollapsed()
{
$type = $this->_getType();
if ($type == 'xml') {
$xml = simplexml_load_file(__DIR__ . '/export/'.$type.'/Doctrine.Tests.ORM.Tools.Export.ExportedUser.dcm.xml');
$xml->registerXPathNamespace("d", "http://doctrine-project.org/schemas/orm/doctrine-mapping");
$nodes = $xml->xpath("/d:doctrine-mapping/d:entity/d:one-to-many[@field='interests']/d:cascade/d:*");
$this->assertEquals(1, count($nodes));
$this->assertEquals('cascade-all', $nodes[0]->getName());
} elseif ($type == 'yaml') {
$yaml = new \Symfony\Component\Yaml\Parser();
$value = $yaml->parse(file_get_contents(__DIR__ . '/export/'.$type.'/Doctrine.Tests.ORM.Tools.Export.ExportedUser.dcm.yml'));
$this->assertTrue(isset($value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade']));
$this->assertEquals(1, count($value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade']));
$this->assertEquals('all', $value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade'][0]);
} else {
$this->markTestSkipped('Test aviable only for '.$type.' dirver');
}
}
public function __destruct() public function __destruct()
{ {
# $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); # $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType());

View File

@ -35,6 +35,16 @@
</order-by> </order-by>
</one-to-many> </one-to-many>
<one-to-many field="interests" target-entity="Doctrine\Tests\ORM\Tools\Export\Interests" mapped-by="user" orphan-removal="true">
<cascade>
<cascade-refresh/>
<cascade-persist/>
<cascade-merge/>
<cascade-detach/>
<cascade-remove/>
</cascade>
</one-to-many>
<many-to-many field="groups" target-entity="Doctrine\Tests\ORM\Tools\Export\Group"> <many-to-many field="groups" target-entity="Doctrine\Tests\ORM\Tools\Export\Group">
<cascade> <cascade>
<cascade-all/> <cascade-all/>

View File

@ -34,6 +34,11 @@ Doctrine\Tests\ORM\Tools\Export\User:
number: ASC number: ASC
cascade: [ persist, merge ] cascade: [ persist, merge ]
orphanRemoval: true orphanRemoval: true
interests:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Interests
mappedBy: user
cascade: [ persist, merge, remove, refresh, detach ]
orphanRemoval: true
manyToMany: manyToMany:
groups: groups:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Group targetEntity: Doctrine\Tests\ORM\Tools\Export\Group

View File

@ -112,6 +112,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\Legacy\LegacyArticle', 'Doctrine\Tests\Models\Legacy\LegacyArticle',
'Doctrine\Tests\Models\Legacy\LegacyCar', 'Doctrine\Tests\Models\Legacy\LegacyCar',
), ),
'customtype' => array(
'Doctrine\Tests\Models\CustomType\CustomTypeChild',
'Doctrine\Tests\Models\CustomType\CustomTypeParent',
'Doctrine\Tests\Models\CustomType\CustomTypeUpperCase',
),
); );
protected function useModelSet($setName) protected function useModelSet($setName)
@ -219,6 +224,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM legacy_users'); $conn->executeUpdate('DELETE FROM legacy_users');
} }
if (isset($this->_usedModelSets['customtype'])) {
$conn->executeUpdate('DELETE FROM customtype_parent_friends');
$conn->executeUpdate('DELETE FROM customtype_parents');
$conn->executeUpdate('DELETE FROM customtype_children');
$conn->executeUpdate('DELETE FROM customtype_uppercases');
}
$this->_em->clear(); $this->_em->clear();
} }

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit>
<php>
<var name="db_type" value="pdo_mysql"/>
<var name="db_host" value="localhost" />
<var name="db_username" value="travis" />
<var name="db_password" value="" />
<var name="db_name" value="doctrine_tests" />
<var name="db_port" value="3306"/>
<var name="tmpdb_type" value="pdo_mysql"/>
<var name="tmpdb_host" value="localhost" />
<var name="tmpdb_username" value="travis" />
<var name="tmpdb_password" value="" />
<var name="tmpdb_name" value="doctrine_tests_tmp" />
<var name="tmpdb_port" value="3306"/>
</php>
<testsuites>
<testsuite name="Doctrine ORM Test Suite">
<directory>./../Doctrine/Tests/ORM</directory>
</testsuite>
</testsuites>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit>
<php>
<!-- "Real" test database -->
<var name="db_type" value="pdo_pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_username" value="postgres" />
<var name="db_password" value="" />
<var name="db_name" value="doctrine_tests" />
<var name="db_port" value="5432"/>
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
<!-- Database for temporary connections (i.e. to drop/create the main database) -->
<var name="tmpdb_type" value="pdo_pgsql"/>
<var name="tmpdb_host" value="localhost" />
<var name="tmpdb_username" value="postgres" />
<var name="tmpdb_password" value="" />
<var name="tmpdb_name" value="doctrine_tests_tmp" />
<var name="tmpdb_port" value="5432"/>
</php>
<testsuites>
<testsuite name="Doctrine ORM Test Suite">
<directory>./../Doctrine/Tests/ORM</directory>
</testsuite>
</testsuites>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit>
<testsuites>
<testsuite name="Doctrine ORM Test Suite">
<directory>./../Doctrine/Tests/ORM</directory>
</testsuite>
</testsuites>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>