1
0
mirror of synced 2025-02-25 16:33:21 +03:00

Merge remote-tracking branch 'doctrine/master' into shesek-patch-1

This commit is contained in:
Alexander 2011-10-17 18:55:48 +02:00
commit fea855004c
140 changed files with 5883 additions and 846 deletions
README.markdownUPGRADE_TO_2_2build.properties.devbuild.xmlcomposer.jsondoctrine-mapping.xsd
lib
tests/Doctrine/Tests

@ -1,6 +1,6 @@
# Doctrine 2 ORM # Doctrine 2 ORM
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ 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

@ -1,3 +1,30 @@
# EntityManager#getPartialReference() creates read-only entity
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they
haven't been in the identity map before. This means objects of this kind never lead to changes
in the UnitOfWork.
# Fields omitted in a partial DQL query or a native query are never updated
Fields of an entity that are not returned from a partial DQL Query or native SQL query
will never be updated through an UPDATE statement.
# Removed support for onUpdate in @JoinColumn # Removed support for onUpdate in @JoinColumn
The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed.
# Changes in Annotation Handling
There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations
from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`:
// Register the ORM Annotations in the AnnotationRegistry
AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
$reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
$driver = new AnnotationDriver($reader, (array)$paths);
$config->setMetadataDriverImpl($driver);

@ -8,6 +8,7 @@ report.dir=reports
log.archive.dir=logs log.archive.dir=logs
project.pirum_dir= project.pirum_dir=
project.download_dir= project.download_dir=
project.xsd_dir=
test.phpunit_configuration_file= test.phpunit_configuration_file=
test.phpunit_generate_coverage=0 test.phpunit_generate_coverage=0
test.pmd_reports=0 test.pmd_reports=0

@ -223,7 +223,12 @@
<target name="distribute-download"> <target name="distribute-download">
<copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" /> <copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" />
<copy file="${dist.dir}/doctrine-orm-${version}.phar" todir="${project.download_dir}" /> <!--<copy file="${dist.dir}/doctrine-orm-${version}.phar" todir="${project.download_dir}" />-->
</target>
<target name="distribute-xsd">
<php expression="substr('${version}', 0, 3)" returnProperty="minorVersion" /><!-- not too robust -->
<copy file="${project.basedir}/doctrine-mapping.xsd" tofile="${project.xsd_dir}/doctrine-mapping-${minorVersion}.xsd" />
</target> </target>
<target name="update-dev-version"> <target name="update-dev-version">
@ -235,7 +240,7 @@
<exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" /> <exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" />
</target> </target>
<target name="release" depends="git-tag,build-packages,package-phar,distribute-download,pirum-release,update-dev-version" /> <target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version,distribute-xsd" />
<!-- <!--
Builds distributable PEAR packages for the Symfony Dependencies Builds distributable PEAR packages for the Symfony Dependencies

20
composer.json Normal file

@ -0,0 +1,20 @@
{
"name": "doctrine/orm",
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "http://www.doctrine-project.org",
"license": "LGPL",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"require": {
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/common": "master-dev",
"doctrine/dbal": "master-dev"
}
}

@ -150,7 +150,7 @@
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/> <xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/> <xs:enumeration value="RESTRICT"/>
<xs:enumeration value="SET_NULL"/> <xs:enumeration value="SET NULL"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>

@ -163,6 +163,16 @@ abstract class AbstractQuery
return $this->_params; return $this->_params;
} }
/**
* Get all defined parameter types.
*
* @return array The defined query parameter types.
*/
public function getParameterTypes()
{
return $this->_paramTypes;
}
/** /**
* Gets a query parameter. * Gets a query parameter.
* *
@ -174,6 +184,17 @@ abstract class AbstractQuery
return isset($this->_params[$key]) ? $this->_params[$key] : null; return isset($this->_params[$key]) ? $this->_params[$key] : null;
} }
/**
* Gets a query parameter type.
*
* @param mixed $key The key (index or name) of the bound parameter.
* @return mixed The parameter type of the bound parameter.
*/
public function getParameterType($key)
{
return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null;
}
/** /**
* Gets the SQL query that corresponds to this query object. * Gets the SQL query that corresponds to this query object.
* The returned SQL syntax depends on the connection driver that is used * The returned SQL syntax depends on the connection driver that is used

@ -516,4 +516,32 @@ class Configuration extends \Doctrine\DBAL\Configuration
} }
return $this->_attributes['classMetadataFactoryName']; return $this->_attributes['classMetadataFactoryName'];
} }
/**
* Set default repository class.
*
* @since 2.2
* @param string $className
* @throws ORMException If not is a Doctrine\ORM\EntityRepository
*/
public function setDefaultRepositoryClassName($className)
{
if ($className != "Doctrine\ORM\EntityRepository" &&
!is_subclass_of($className, 'Doctrine\ORM\EntityRepository')){
throw ORMException::invalidEntityRepository($className);
}
$this->_attributes['defaultRepositoryClassName'] = $className;
}
/**
* Get default repository class.
*
* @since 2.2
* @return string
*/
public function getDefaultRepositoryClassName()
{
return isset($this->_attributes['defaultRepositoryClassName']) ?
$this->_attributes['defaultRepositoryClassName'] : 'Doctrine\ORM\EntityRepository';
}
} }

@ -413,6 +413,7 @@ class EntityManager implements ObjectManager
$entity = $class->newInstance(); $entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier); $class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array()); $this->unitOfWork->registerManaged($entity, $identifier, array());
$this->unitOfWork->markReadOnly($entity);
return $entity; return $entity;
} }
@ -421,16 +422,11 @@ class EntityManager implements ObjectManager
* Clears the EntityManager. All entities that are currently managed * Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached. * by this EntityManager become detached.
* *
* @param string $entityName * @param string $entityName if given, only entities of this type will get detached
*/ */
public function clear($entityName = null) public function clear($entityName = null)
{ {
if ($entityName === null) { $this->unitOfWork->clear($entityName);
$this->unitOfWork->clear();
} else {
//TODO
throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
}
} }
/** /**
@ -576,7 +572,8 @@ class EntityManager implements ObjectManager
if ($customRepositoryClassName !== null) { if ($customRepositoryClassName !== null) {
$repository = new $customRepositoryClassName($this, $metadata); $repository = new $customRepositoryClassName($this, $metadata);
} else { } else {
$repository = new EntityRepository($this, $metadata); $repositoryClass = $this->config->getDefaultRepositoryClassName();
$repository = new $repositoryClass($this, $metadata);
} }
$this->repositories[$entityName] = $repository; $this->repositories[$entityName] = $repository;
@ -712,6 +709,18 @@ class EntityManager implements ObjectManager
return $this->proxyFactory; return $this->proxyFactory;
} }
/**
* Helper method to initialize a lazy loading proxy or persistent collection.
*
* This method is a no-op for other objects
*
* @param object $obj
*/
public function initializeObject($obj)
{
$this->unitOfWork->initializeObject($obj);
}
/** /**
* Factory method to create EntityManager instances. * Factory method to create EntityManager instances.
* *

@ -178,7 +178,7 @@ class EntityRepository implements ObjectRepository
*/ */
public function findOneBy(array $criteria) public function findOneBy(array $criteria)
{ {
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria); return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria, null, null, array(), 0, 1);
} }
/** /**

@ -0,0 +1,110 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Event;
use \Doctrine\Common\EventSubscriber;
use \LogicException;
/**
* Delegate events only for certain entities they are registered for.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.2
*/
class EntityEventDelegator implements EventSubscriber
{
/**
* Keeps track of all the event listeners.
*
* @var array
*/
private $listeners = array();
/**
* If frozen no new event listeners can be added.
*
* @var bool
*/
private $frozen = false;
/**
* Adds an event listener that listens on the specified events.
*
* @param string|array $events The event(s) to listen on.
* @param string|array $entities The entities to trigger this listener for
* @param object $listener The listener object.
*/
public function addEventListener($events, $entities, $listener)
{
if ($this->frozen) {
throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " .
"is called once. This happens when you register the delegator with the event manager.");
}
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);
foreach ((array) $events as $event) {
// Overrides listener if a previous one was associated already
// Prevents duplicate listeners on same event (same instance only)
$this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities));
}
}
/**
* Adds an EventSubscriber. The subscriber is asked for all the events he is
* interested in and added as a listener for these events.
*
* @param Doctrine\Common\EventSubscriber $subscriber The subscriber.
*/
public function addEventSubscriber(EventSubscriber $subscriber, $entities)
{
$this->addEventListener($subscriber->getSubscribedEvents(), $entities, $subscriber);
}
/**
* Returns an array of events this subscriber wants to listen to.
*
* @return array
*/
public function getSubscribedEvents()
{
$this->frozen = true;
return array_keys($this->listeners);
}
/**
* Delegate the event to an appropriate listener
*
* @param $eventName
* @param $event
* @return void
*/
public function __call($eventName, $args)
{
$event = $args[0];
foreach ($this->listeners[$eventName] AS $listenerData) {
$class = get_class($event->getEntity());
if (isset($listenerData['entities'][$class])) {
$listenerData['listener']->$eventName($event);
}
}
}
}

@ -36,12 +36,18 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
*/ */
private $em; private $em;
/**
* @var string
*/
private $entityClass;
/** /**
* @param \Doctrine\ORM\EntityManager $em * @param \Doctrine\ORM\EntityManager $em
*/ */
public function __construct($em) public function __construct($em, $entityClass = null)
{ {
$this->em = $em; $this->em = $em;
$this->entityClass = $entityClass;
} }
/** /**
@ -51,4 +57,24 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
{ {
return $this->em; return $this->em;
} }
/**
* Name of the entity class that is cleared, or empty if all are cleared.
*
* @return string
*/
public function getEntityClass()
{
return $this->entityClass;
}
/**
* Check if event clears all entities.
*
* @return bool
*/
public function clearsAllEntities()
{
return $this->entityClass === null;
}
} }

@ -90,7 +90,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
if (!isset($this->_entityChangeSet[$field])) { if (!isset($this->_entityChangeSet[$field])) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
"Field '".$field."' is not a valid field of the entity ". "Field '".$field."' is not a valid field of the entity ".
"'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs." "'".get_class($this->getEntity())."' in PreUpdateEventArgs."
); );
} }
} }

@ -60,7 +60,7 @@ class AssignedGenerator extends AbstractIdGenerator
$identifier[$idField] = $value; $identifier[$idField] = $value;
} }
} else { } else {
throw ORMException::entityMissingAssignedId($entity); throw ORMException::entityMissingAssignedIdForField($entity, $idField);
} }
} }
} else { } else {
@ -78,7 +78,7 @@ class AssignedGenerator extends AbstractIdGenerator
$identifier[$idField] = $value; $identifier[$idField] = $value;
} }
} else { } else {
throw ORMException::entityMissingAssignedId($entity); throw ORMException::entityMissingAssignedIdForField($entity, $idField);
} }
} }

@ -164,6 +164,11 @@ abstract class AbstractHydrator
* field names during this procedure as well as any necessary conversions on * field names during this procedure as well as any necessary conversions on
* the values applied. * the values applied.
* *
* @param array $data SQL Result Row
* @param array &$cache Cache for column to field result information
* @param array &$id Dql-Alias => ID-Hash
* @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
*
* @return array An array with all the fields (name => value) of the data row, * @return array An array with all the fields (name => value) of the data row,
* grouped by their component alias. * grouped by their component alias.
*/ */

@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator
$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.
if ( ! isset($nonemptyComponents[$parent]) ) {
continue;
}
// Get a reference to the right element in the result tree. // Get a reference to the right element in the result tree.
// This element will get the associated element attached. // This element will get the associated element attached.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
@ -155,6 +160,17 @@ class ArrayHydrator extends AbstractHydrator
$this->_rootAliases[$dqlAlias] = true; // Mark as root $this->_rootAliases[$dqlAlias] = true; // Mark as root
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) {
$result[] = array(0 => null);
} else {
$result[] = null;
}
++$this->_resultCounter;
continue;
}
// Check for an existing element // Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias]; $element = $rowData[$dqlAlias];

@ -302,6 +302,12 @@ class ObjectHydrator extends AbstractHydrator
// seen for this parent-child relationship // seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias; $path = $parentAlias . '.' . $dqlAlias;
// We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
if (!isset($nonemptyComponents[$parentAlias])) {
// TODO: Add special case code where we hydrate the right join objects into identity map at least
continue;
}
// 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);
@ -408,6 +414,18 @@ class ObjectHydrator extends AbstractHydrator
// 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
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) {
$result[] = array(0 => null);
} else {
$result[] = null;
}
++$this->_resultCounter;
continue;
}
// check for existing result from the iterations before
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 (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {

@ -0,0 +1,167 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Builder;
use Doctrine\ORM\Mapping\ClassMetadata;
class AssociationBuilder
{
/**
* @var ClassMetadataBuilder
*/
protected $builder;
/**
* @var array
*/
protected $mapping;
/**
* @var array
*/
protected $joinColumns;
/**
*
* @var int
*/
protected $type;
/**
* @param ClassMetadataBuilder $builder
* @param array $mapping
*/
public function __construct(ClassMetadataBuilder $builder, array $mapping, $type)
{
$this->builder = $builder;
$this->mapping = $mapping;
$this->type = $type;
}
public function mappedBy($fieldName)
{
$this->mapping['mappedBy'] = $fieldName;
return $this;
}
public function inversedBy($fieldName)
{
$this->mapping['inversedBy'] = $fieldName;
return $this;
}
public function cascadeAll()
{
$this->mapping['cascade'] = array("ALL");
return $this;
}
public function cascadePersist()
{
$this->mapping['cascade'][] = "persist";
return $this;
}
public function cascadeRemove()
{
$this->mapping['cascade'][] = "remove";
return $this;
}
public function cascadeMerge()
{
$this->mapping['cascade'][] = "merge";
return $this;
}
public function cascadeDetach()
{
$this->mapping['cascade'][] = "detach";
return $this;
}
public function cascadeRefresh()
{
$this->mapping['cascade'][] = "refresh";
return $this;
}
public function fetchExtraLazy()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY;
return $this;
}
public function fetchEager()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_EAGER;
return $this;
}
public function fetchLazy()
{
$this->mapping['fetch'] = ClassMetadata::FETCH_LAZY;
return $this;
}
/**
* Add Join Columns
*
* @param string $columnName
* @param string $referencedColumnName
* @param bool $nullable
* @param bool $unique
* @param string $onDelete
* @param string $columnDef
*/
public function addJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
{
$this->joinColumns[] = array(
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'nullable' => $nullable,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,
);
return $this;
}
/**
* @return ClassMetadataBuilder
*/
public function build()
{
$mapping = $this->mapping;
if ($this->joinColumns) {
$mapping['joinColumns'] = $this->joinColumns;
}
$cm = $this->builder->getClassMetadata();
if ($this->type == ClassMetadata::MANY_TO_ONE) {
$cm->mapManyToOne($mapping);
} else if ($this->type == ClassMetadata::ONE_TO_ONE) {
$cm->mapOneToOne($mapping);
} else {
throw new \InvalidArgumentException("Type should be a ToOne Assocation here");
}
return $this->builder;
}
}

@ -0,0 +1,407 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Builder;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Builder Object for ClassMetadata
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class ClassMetadataBuilder
{
/**
* @var ClassMetadata
*/
private $cm;
/**
* @param ClassMetadata $cm
*/
public function __construct(ClassMetadata $cm)
{
$this->cm = $cm;
}
/**
* @return ClassMetadata
*/
public function getClassMetadata()
{
return $this->cm;
}
/**
* Mark the class as mapped superclass.
*
* @return ClassMetadataBuilder
*/
public function setMappedSuperClass()
{
$this->cm->isMappedSuperclass = true;
return $this;
}
/**
* Set custom Repository class name
*
* @param string $repositoryClassName
* @return ClassMetadataBuilder
*/
public function setCustomRepositoryClass($repositoryClassName)
{
$this->cm->setCustomRepositoryClass($repositoryClassName);
return $this;
}
/**
* Mark class read only
*
* @return ClassMetadataBuilder
*/
public function setReadOnly()
{
$this->cm->markReadOnly();
return $this;
}
/**
* Set the table name
*
* @param string $name
* @return ClassMetadataBuilder
*/
public function setTable($name)
{
$this->cm->setPrimaryTable(array('name' => $name));
return $this;
}
/**
* Add Index
*
* @param array $columns
* @param string $name
* @return ClassMetadataBuilder
*/
public function addIndex(array $columns, $name)
{
if (!isset($this->cm->table['indexes'])) {
$this->cm->table['indexes'] = array();
}
$this->cm->table['indexes'][$name] = array('columns' => $columns);
return $this;
}
/**
* Add Unique Constraint
*
* @param array $columns
* @param string $name
* @return ClassMetadataBuilder
*/
public function addUniqueConstraint(array $columns, $name)
{
if (!isset($this->cm->table['uniqueConstraints'])) {
$this->cm->table['uniqueConstraints'] = array();
}
$this->cm->table['uniqueConstraints'][$name] = array('columns' => $columns);
return $this;
}
/**
* Add named query
*
* @param string $name
* @param string $dqlQuery
* @return ClassMetadataBuilder
*/
public function addNamedQuery($name, $dqlQuery)
{
$this->cm->addNamedQuery(array(
'name' => $name,
'query' => $dqlQuery,
));
return $this;
}
/**
* Set class as root of a joined table inheritance hierachy.
*
* @return ClassMetadataBuilder
*/
public function setJoinedTableInheritance()
{
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED);
return $this;
}
/**
* Set class as root of a single table inheritance hierachy.
*
* @return ClassMetadataBuilder
*/
public function setSingleTableInheritance()
{
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE);
return $this;
}
/**
* Set the discriminator column details.
*
* @param string $name
* @param string $type
*/
public function setDiscriminatorColumn($name, $type = 'string', $length = 255)
{
$this->cm->setDiscriminatorColumn(array(
'name' => $name,
'type' => $type,
'length' => $length,
));
return $this;
}
/**
* Add a subclass to this inheritance hierachy.
*
* @param string $name
* @param string $class
* @return ClassMetadataBuilder
*/
public function addDiscriminatorMapClass($name, $class)
{
$this->cm->addDiscriminatorMapClass($name, $class);
return $this;
}
/**
* Set deferred explicit change tracking policy.
*
* @return ClassMetadataBuilder
*/
public function setChangeTrackingPolicyDeferredExplicit()
{
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT);
return $this;
}
/**
* Set notify change tracking policy.
*
* @return ClassMetadataBuilder
*/
public function setChangeTrackingPolicyNotify()
{
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY);
return $this;
}
/**
* Add lifecycle event
*
* @param string $methodName
* @param string $event
* @return ClassMetadataBuilder
*/
public function addLifecycleEvent($methodName, $event)
{
$this->cm->addLifecycleCallback($methodName, $event);
return $this;
}
/**
* Add Field
*
* @param string $name
* @param string $type
* @param array $mapping
*/
public function addField($name, $type, array $mapping = array())
{
$mapping['fieldName'] = $name;
$mapping['type'] = $type;
$this->cm->mapField($mapping);
return $this;
}
/**
* Create a field builder.
*
* @param string $name
* @param string $type
* @return FieldBuilder
*/
public function createField($name, $type)
{
return new FieldBuilder($this, array('fieldName' => $name, 'type' => $type));
}
/**
* Add a simple many to one association, optionally with the inversed by field.
*
* @param string $name
* @param string $targetEntity
* @param string|null $inversedBy
* @return ClassMetadataBuilder
*/
public function addManyToOne($name, $targetEntity, $inversedBy = null)
{
$builder = $this->createManyToOne($name, $targetEntity);
if ($inversedBy) {
$builder->setInversedBy($inversedBy);
}
return $builder->build();
}
/**
* Create a ManyToOne Assocation Builder.
*
* Note: This method does not add the association, you have to call build() on the AssociationBuilder.
*
* @param string $name
* @param string $targetEntity
* @return AssociationBuilder
*/
public function createManyToOne($name, $targetEntity)
{
return new AssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::MANY_TO_ONE);
}
/**
* Create OneToOne Assocation Builder
*
* @param string $name
* @param string $targetEntity
* @return AssociationBuilder
*/
public function createOneToOne($name, $targetEntity)
{
return new AssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::ONE_TO_ONE);
}
/**
* Add simple inverse one-to-one assocation.
*
* @param string $name
* @param string $targetEntity
* @param string $mappedBy
* @return ClassMetadataBuilder
*/
public function addInverseOneToOne($name, $targetEntity, $mappedBy)
{
$builder = $this->createOneToOne($name, $targetEntity);
$builder->setMappedBy($mappedBy);
return $builder->build();
}
/**
* Add simple owning one-to-one assocation.
*
* @param string $name
* @param string $targetEntity
* @param string $inversedBy
* @return ClassMetadataBuilder
*/
public function addOwningOneToOne($name, $targetEntity, $inversedBy = null)
{
$builder = $this->createOneToOne($name, $targetEntity);
if ($inversedBy) {
$builder->setInversedBy($inversedBy);
}
return $builder->build();
}
/**
* Create ManyToMany Assocation Builder
*
* @param string $name
* @param string $targetEntity
* @return ManyToManyAssociationBuilder
*/
public function createManyToMany($name, $targetEntity)
{
return new ManyToManyAssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::MANY_TO_MANY);
}
/**
* Add a simple owning many to many assocation.
*
* @param string $name
* @param string $targetEntity
* @param string|null $inversedBy
* @return ClassMetadataBuilder
*/
public function addOwningManyToMany($name, $targetEntity, $inversedBy = null)
{
$builder = $this->createManyToMany($name, $targetEntity);
if ($inversedBy) {
$builder->setInversedBy($inversedBy);
}
return $builder->build();
}
/**
* Add a simple inverse many to many assocation.
*
* @param string $name
* @param string $targetEntity
* @param string $mappedBy
* @return ClassMetadataBuilder
*/
public function addInverseManyToMany($name, $targetEntity, $mappedBy)
{
$builder = $this->createManyToMany($name, $targetEntity);
$builder->setMappedBy($mappedBy);
return $builder->build();
}
/**
* Create a one to many assocation builder
*
* @param string $name
* @param string $targetEntity
* @return OneToManyAssociationBuilder
*/
public function createOneToMany($name, $targetEntity)
{
return new OneToManyAssociationBuilder($this, array('fieldName' => $name, 'targetEntity' => $targetEntity), ClassMetadata::ONE_TO_MANY);
}
/**
* Add simple OneToMany assocation.
*
* @param string $name
* @param string $targetEntity
* @param string $mappedBy
* @return ClassMetadataBuilder
*/
public function addOneToMany($name, $targetEntity, $mappedBy)
{
$builder = $this->createOneToMany($name, $targetEntity);
$builder->setMappedBy($mappedBy);
return $builder->build();
}
}

@ -0,0 +1,223 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Builder;
/**
* Field Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class FieldBuilder
{
/**
* @var ClassMetadataBuilder
*/
private $builder;
/**
* @var array
*/
private $mapping;
/**
* @var bool
*/
private $version;
/**
* @var string
*/
private $generatedValue;
/**
* @var array
*/
private $sequenceDef;
/**
*
* @param ClassMetadataBuilder $builder
* @param array $mapping
*/
public function __construct(ClassMetadataBuilder $builder, array $mapping)
{
$this->builder = $builder;
$this->mapping = $mapping;
}
/**
* Set length.
*
* @param int $length
* @return FieldBuilder
*/
public function length($length)
{
$this->mapping['length'] = $length;
return $this;
}
/**
* Set nullable
*
* @param bool
* @return FieldBuilder
*/
public function nullable($flag = true)
{
$this->mapping['nullable'] = (bool)$flag;
return $this;
}
/**
* Set Unique
*
* @param bool
* @return FieldBuilder
*/
public function unique($flag = true)
{
$this->mapping['unique'] = (bool)$flag;
return $this;
}
/**
* Set column name
*
* @param string $name
* @return FieldBuilder
*/
public function columnName($name)
{
$this->mapping['columnName'] = $name;
return $this;
}
/**
* Set Precision
*
* @param int $p
* @return FieldBuilder
*/
public function precision($p)
{
$this->mapping['precision'] = $p;
return $this;
}
/**
* Set scale.
*
* @param int $s
* @return FieldBuilder
*/
public function scale($s)
{
$this->mapping['scale'] = $s;
return $this;
}
/**
* Set field as primary key.
*
* @return FieldBuilder
*/
public function isPrimaryKey()
{
$this->mapping['id'] = true;
return $this;
}
/**
* @param int $strategy
* @return FieldBuilder
*/
public function generatedValue($strategy = 'AUTO')
{
$this->generatedValue = $strategy;
return $this;
}
/**
* Set field versioned
*
* @return FieldBuilder
*/
public function isVersionField()
{
$this->version = true;
return $this;
}
/**
* Set Sequence Generator
*
* @param string $sequenceName
* @param int $allocationSize
* @param int $initialValue
* @return FieldBuilder
*/
public function setSequenceGenerator($sequenceName, $allocationSize = 1, $initialValue = 1)
{
$this->sequenceDef = array(
'sequenceName' => $sequenceName,
'allocationSize' => $allocationSize,
'initialValue' => $initialValue,
);
return $this;
}
/**
* Set column definition.
*
* @param string $def
* @return FieldBuilder
*/
public function columnDefinition($def)
{
$this->mapping['columnDefinition'] = $def;
return $this;
}
/**
* Finalize this field and attach it to the ClassMetadata.
*
* Without this call a FieldBuilder has no effect on the ClassMetadata.
*
* @return ClassMetadataBuilder
*/
public function build()
{
$cm = $this->builder->getClassMetadata();
if ($this->generatedValue) {
$cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue));
}
if ($this->version) {
$cm->setVersionMapping($this->mapping);
}
$cm->mapField($this->mapping);
if ($this->sequenceDef) {
$cm->setSequenceGeneratorDefinition($this->sequenceDef);
}
return $this->builder;
}
}

@ -0,0 +1,86 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Builder;
/**
* ManyToMany Association Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
{
private $joinTableName;
private $inverseJoinColumns = array();
public function setJoinTable($name)
{
$this->joinTableName = $name;
return $this;
}
/**
* Add Inverse Join Columns
*
* @param string $columnName
* @param string $referencedColumnName
* @param bool $nullable
* @param bool $unique
* @param string $onDelete
* @param string $columnDef
*/
public function addInverseJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
{
$this->inverseJoinColumns[] = array(
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'nullable' => $nullable,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,
);
return $this;
}
/**
* @return ClassMetadataBuilder
*/
public function build()
{
$mapping = $this->mapping;
$mapping['joinTable'] = array();
if ($this->joinColumns) {
$mapping['joinTable']['joinColumns'] = $this->joinColumns;
}
if ($this->inverseJoinColumns) {
$mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns;
}
if ($this->joinTableName) {
$mapping['joinTable']['name'] = $this->joinTableName;
}
$cm = $this->builder->getClassMetadata();
$cm->mapManyToMany($mapping);
return $this->builder;
}
}

@ -0,0 +1,62 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Builder;
/**
* OneToMany Association Builder
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OneToManyAssociationBuilder extends AssociationBuilder
{
/**
* @param array $fieldNames
* @return OneToManyAssociationBuilder
*/
public function setOrderBy(array $fieldNames)
{
$this->mapping['orderBy'] = $fieldNames;
return $this;
}
public function setIndexBy($fieldName)
{
$this->mapping['indexBy'] = $fieldName;
return $this;
}
/**
* @return ClassMetadataBuilder
*/
public function build()
{
$mapping = $this->mapping;
if ($this->joinColumns) {
$mapping['joinColumns'] = $this->joinColumns;
}
$cm = $this->builder->getClassMetadata();
$cm->mapOneToMany($mapping);
return $this->builder;
}
}

@ -343,4 +343,17 @@ class ClassMetadata extends ClassMetadataInfo
} }
return clone $this->_prototype; return clone $this->_prototype;
} }
/**
* @param string $callback
* @param string $event
*/
public function addLifecycleCallback($callback, $event)
{
if ( !$this->reflClass->hasMethod($callback) ||
($this->reflClass->getMethod($callback)->getModifiers() & \ReflectionMethod::IS_PUBLIC) == 0) {
throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callback);
}
return parent::addLifecycleCallback($callback, $event);
}
} }

@ -50,7 +50,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
private $targetPlatform; private $targetPlatform;
/** /**
* @var Driver\Driver * @var \Doctrine\ORM\Mapping\Driver\Driver
*/ */
private $driver; private $driver;
@ -274,6 +274,9 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$class->setDiscriminatorMap($parent->discriminatorMap); $class->setDiscriminatorMap($parent->discriminatorMap);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks); $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy); $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
if ($parent->isMappedSuperclass) {
$class->setCustomRepositoryClass($parent->customRepositoryClassName);
}
} }
// Invoke driver // Invoke driver
@ -306,6 +309,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$class->setPrimaryTable($parent->table); $class->setPrimaryTable($parent->table);
} }
if ($parent && $parent->containsForeignIdentifier) {
$class->containsForeignIdentifier = true;
}
$class->setParentClasses($visited); $class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) { if ($this->evm->hasListeners(Events::loadClassMetadata)) {
@ -448,7 +455,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
// <table>_<column>_seq in PostgreSQL for SERIAL columns. // <table>_<column>_seq in PostgreSQL for SERIAL columns.
// Not pretty but necessary and the simplest solution that currently works. // Not pretty but necessary and the simplest solution that currently works.
$seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ? $seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
$class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' : $class->getTableName() . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
null; null;
$class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName)); $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
break; break;
@ -478,4 +485,15 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
throw new ORMException("Unknown generator type: " . $class->generatorType); throw new ORMException("Unknown generator type: " . $class->generatorType);
} }
} }
/**
* Check if this class is mapped by this EntityManager + ClassMetadata configuration
*
* @param $class
* @return bool
*/
public function isTransient($class)
{
return $this->driver->isTransient($class);
}
} }

@ -774,11 +774,15 @@ class ClassMetadataInfo implements ClassMetadata
// If targetEntity is unqualified, assume it is in the same namespace as // If targetEntity is unqualified, assume it is in the same namespace as
// the sourceEntity. // the sourceEntity.
$mapping['sourceEntity'] = $this->name; $mapping['sourceEntity'] = $this->name;
if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false
&& strlen($this->namespace) > 0) { if (isset($mapping['targetEntity'])) {
if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
} }
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
}
// 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) {
@ -904,9 +908,8 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
} }
//TODO: if orphanRemoval, cascade=remove is implicit! $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
(bool) $mapping['orphanRemoval'] : false;
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) { if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']); throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
@ -931,9 +934,8 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
} }
//TODO: if orphanRemoval, cascade=remove is implicit! $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
(bool) $mapping['orphanRemoval'] : false;
if (isset($mapping['orderBy'])) { if (isset($mapping['orderBy'])) {
if ( ! is_array($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) {
@ -1268,7 +1270,7 @@ class ClassMetadataInfo implements ClassMetadata
public function getTemporaryIdTableName() public function getTemporaryIdTableName()
{ {
// replace dots with underscores because PostgreSQL creates temporary tables in a special schema // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
return str_replace('.', '_', $this->table['name'] . '_id_tmp'); return str_replace('.', '_', $this->getTableName() . '_id_tmp');
} }
/** /**
@ -1367,9 +1369,11 @@ class ClassMetadataInfo implements ClassMetadata
$this->table['name'] = $table['name']; $this->table['name'] = $table['name'];
} }
} }
if (isset($table['indexes'])) { if (isset($table['indexes'])) {
$this->table['indexes'] = $table['indexes']; $this->table['indexes'] = $table['indexes'];
} }
if (isset($table['uniqueConstraints'])) { if (isset($table['uniqueConstraints'])) {
$this->table['uniqueConstraints'] = $table['uniqueConstraints']; $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
} }
@ -1564,9 +1568,6 @@ class ClassMetadataInfo implements ClassMetadata
/** /**
* Adds a lifecycle callback for entities of this class. * Adds a lifecycle callback for entities of this class.
* *
* Note: If the same callback is registered more than once, the old one
* will be overridden.
*
* @param string $callback * @param string $callback
* @param string $event * @param string $event
*/ */
@ -1625,13 +1626,27 @@ class ClassMetadataInfo implements ClassMetadata
public function setDiscriminatorMap(array $map) public function setDiscriminatorMap(array $map)
{ {
foreach ($map as $value => $className) { foreach ($map as $value => $className) {
if (strpos($className, '\\') === false && strlen($this->namespace)) { $this->addDiscriminatorMapClass($value, $className);
}
}
/**
* Add one entry of the discriminator map with a new class and corresponding name.
*
* @param string $name
* @param string $className
*/
public function addDiscriminatorMapClass($name, $className)
{
if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) {
$className = $this->namespace . '\\' . $className; $className = $this->namespace . '\\' . $className;
} }
$className = ltrim($className, '\\'); $className = ltrim($className, '\\');
$this->discriminatorMap[$value] = $className; $this->discriminatorMap[$name] = $className;
if ($this->name == $className) { if ($this->name == $className) {
$this->discriminatorValue = $value; $this->discriminatorValue = $name;
} else { } else {
if ( ! class_exists($className)) { if ( ! class_exists($className)) {
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
@ -1641,7 +1656,6 @@ class ClassMetadataInfo implements ClassMetadata
} }
} }
} }
}
/** /**
* Checks whether the class has a named query with the given query name. * Checks whether the class has a named query with the given query name.
@ -1873,9 +1887,10 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getAssociationTargetClass($assocName) public function getAssociationTargetClass($assocName)
{ {
if (!isset($this->associationMappings[$assocName])) { if ( ! isset($this->associationMappings[$assocName])) {
throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association."); throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
} }
return $this->associationMappings[$assocName]['targetEntity']; return $this->associationMappings[$assocName]['targetEntity'];
} }
@ -1899,9 +1914,7 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getQuotedColumnName($field, $platform) public function getQuotedColumnName($field, $platform)
{ {
return isset($this->fieldMappings[$field]['quoted']) ? return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName'];
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
$this->fieldMappings[$field]['columnName'];
} }
/** /**
@ -1913,9 +1926,7 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getQuotedTableName($platform) public function getQuotedTableName($platform)
{ {
return isset($this->table['quoted']) ? return isset($this->table['quoted']) ? $platform->quoteIdentifier($this->table['name']) : $this->table['name'];
$platform->quoteIdentifier($this->table['name']) :
$this->table['name'];
} }
/** /**
@ -1926,8 +1937,6 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public function getQuotedJoinTableName(array $assoc, $platform) public function getQuotedJoinTableName(array $assoc, $platform)
{ {
return isset($assoc['joinTable']['quoted']) return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name'];
? $platform->quoteIdentifier($assoc['joinTable']['name'])
: $assoc['joinTable']['name'];
} }
} }

@ -147,12 +147,15 @@ class AnnotationDriver implements Driver
// Evaluate Entity annotation // Evaluate Entity annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity']; $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
if ($entityAnnot->repositoryClass !== null) {
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass); $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
}
if ($entityAnnot->readOnly) { if ($entityAnnot->readOnly) {
$metadata->markReadOnly(); $metadata->markReadOnly();
} }
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) { } else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
$mappedSuperclassAnnot = $classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'];
$metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
$metadata->isMappedSuperclass = true; $metadata->isMappedSuperclass = true;
} else { } else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);

@ -19,7 +19,8 @@
namespace Doctrine\ORM\Mapping; namespace Doctrine\ORM\Mapping;
use Doctrine\Common\Annotations\Annotation; interface Annotation {}
/* Annotations */ /* Annotations */
@ -27,8 +28,10 @@ use Doctrine\Common\Annotations\Annotation;
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class Entity extends Annotation { final class Entity implements Annotation {
/** @var string */
public $repositoryClass; public $repositoryClass;
/** @var boolean */
public $readOnly = false; public $readOnly = false;
} }
@ -36,42 +39,56 @@ final class Entity extends Annotation {
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class MappedSuperclass extends Annotation {} final class MappedSuperclass implements Annotation {
/** @var string */
/** public $repositoryClass;
* @Annotation
* @Target("CLASS")
*/
final class InheritanceType extends Annotation {}
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorColumn extends Annotation {
public $name;
public $fieldName; // field name used in non-object hydration (array/scalar)
public $type;
public $length;
} }
/** /**
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class DiscriminatorMap extends Annotation {} final class InheritanceType implements Annotation {
/** @var string */
public $value;
}
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorColumn implements Annotation {
/** @var string */
public $name;
/** @var string */
public $type;
/** @var integer */
public $length;
/** @var mixed */
public $fieldName; // field name used in non-object hydration (array/scalar)
}
/**
* @Annotation
* @Target("CLASS")
*/
final class DiscriminatorMap implements Annotation {
/** @var array<string> */
public $value;
}
/** /**
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class Id extends Annotation {} final class Id implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class GeneratedValue extends Annotation { final class GeneratedValue implements Annotation {
/** @var string */
public $strategy = 'AUTO'; public $strategy = 'AUTO';
} }
@ -79,43 +96,60 @@ final class GeneratedValue extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class Version extends Annotation {} final class Version implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target({"PROPERTY","ANNOTATION"}) * @Target({"PROPERTY","ANNOTATION"})
*/ */
final class JoinColumn extends Annotation { final class JoinColumn implements Annotation {
/** @var string */
public $name; public $name;
public $fieldName; // field name used in non-object hydration (array/scalar) /** @var string */
public $referencedColumnName = 'id'; public $referencedColumnName = 'id';
/** @var boolean */
public $unique = false; public $unique = false;
/** @var boolean */
public $nullable = true; public $nullable = true;
/** @var mixed */
public $onDelete; public $onDelete;
/** @var string */
public $columnDefinition; public $columnDefinition;
/** @var string */
public $fieldName; // field name used in non-object hydration (array/scalar)
} }
/** /**
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class JoinColumns extends Annotation {} final class JoinColumns implements Annotation {
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $value;
}
/** /**
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class Column extends Annotation { final class Column implements Annotation {
public $type = 'string'; /** @var string */
public $length;
// The precision for a decimal (exact numeric) column (Applies only for decimal column)
public $precision = 0;
// The scale for a decimal (exact numeric) column (Applies only for decimal column)
public $scale = 0;
public $unique = false;
public $nullable = false;
public $name; public $name;
/** @var mixed */
public $type = 'string';
/** @var integer */
public $length;
/** @var integer */
public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column)
/** @var integer */
public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column)
/** @var boolean */
public $unique = false;
/** @var boolean */
public $nullable = false;
/** @var array */
public $options = array(); public $options = array();
/** @var string */
public $columnDefinition; public $columnDefinition;
} }
@ -123,12 +157,18 @@ final class Column extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class OneToOne extends Annotation { final class OneToOne implements Annotation {
/** @var string */
public $targetEntity; public $targetEntity;
/** @var string */
public $mappedBy; public $mappedBy;
/** @var string */
public $inversedBy; public $inversedBy;
/** @var array<string> */
public $cascade; public $cascade;
/** @var string */
public $fetch = 'LAZY'; public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false; public $orphanRemoval = false;
} }
@ -136,12 +176,18 @@ final class OneToOne extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class OneToMany extends Annotation { final class OneToMany implements Annotation {
/** @var string */
public $mappedBy; public $mappedBy;
/** @var string */
public $targetEntity; public $targetEntity;
/** @var array<string> */
public $cascade; public $cascade;
/** @var string */
public $fetch = 'LAZY'; public $fetch = 'LAZY';
/** @var boolean */
public $orphanRemoval = false; public $orphanRemoval = false;
/** @var string */
public $indexBy; public $indexBy;
} }
@ -149,10 +195,14 @@ final class OneToMany extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class ManyToOne extends Annotation { final class ManyToOne implements Annotation {
/** @var string */
public $targetEntity; public $targetEntity;
/** @var array<string> */
public $cascade; public $cascade;
/** @var string */
public $fetch = 'LAZY'; public $fetch = 'LAZY';
/** @var string */
public $inversedBy; public $inversedBy;
} }
@ -160,12 +210,18 @@ final class ManyToOne extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class ManyToMany extends Annotation { final class ManyToMany implements Annotation {
/** @var string */
public $targetEntity; public $targetEntity;
/** @var string */
public $mappedBy; public $mappedBy;
/** @var string */
public $inversedBy; public $inversedBy;
/** @var array<string> */
public $cascade; public $cascade;
/** @var string */
public $fetch = 'LAZY'; public $fetch = 'LAZY';
/** @var string */
public $indexBy; public $indexBy;
} }
@ -174,7 +230,8 @@ final class ManyToMany extends Annotation {
* @Target("ALL") * @Target("ALL")
* @todo check available targets * @todo check available targets
*/ */
final class ElementCollection extends Annotation { final class ElementCollection implements Annotation {
/** @var string */
public $tableName; public $tableName;
} }
@ -182,10 +239,14 @@ final class ElementCollection extends Annotation {
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class Table extends Annotation { final class Table implements Annotation {
/** @var string */
public $name; public $name;
/** @var string */
public $schema; public $schema;
/** @var array<Doctrine\ORM\Mapping\Index> */
public $indexes; public $indexes;
/** @var array<Doctrine\ORM\Mapping\UniqueConstraint> */
public $uniqueConstraints; public $uniqueConstraints;
} }
@ -193,8 +254,10 @@ final class Table extends Annotation {
* @Annotation * @Annotation
* @Target("ANNOTATION") * @Target("ANNOTATION")
*/ */
final class UniqueConstraint extends Annotation { final class UniqueConstraint implements Annotation {
/** @var string */
public $name; public $name;
/** @var array<string> */
public $columns; public $columns;
} }
@ -202,8 +265,10 @@ final class UniqueConstraint extends Annotation {
* @Annotation * @Annotation
* @Target("ANNOTATION") * @Target("ANNOTATION")
*/ */
final class Index extends Annotation { final class Index implements Annotation {
/** @var string */
public $name; public $name;
/** @var array<string> */
public $columns; public $columns;
} }
@ -211,10 +276,14 @@ final class Index extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class JoinTable extends Annotation { final class JoinTable implements Annotation {
/** @var string */
public $name; public $name;
/** @var string */
public $schema; public $schema;
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $joinColumns = array(); public $joinColumns = array();
/** @var array<Doctrine\ORM\Mapping\JoinColumn> */
public $inverseJoinColumns = array(); public $inverseJoinColumns = array();
} }
@ -222,9 +291,12 @@ final class JoinTable extends Annotation {
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class SequenceGenerator extends Annotation { final class SequenceGenerator implements Annotation {
/** @var string */
public $sequenceName; public $sequenceName;
/** @var integer */
public $allocationSize = 1; public $allocationSize = 1;
/** @var integer */
public $initialValue = 1; public $initialValue = 1;
} }
@ -232,26 +304,37 @@ final class SequenceGenerator extends Annotation {
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class ChangeTrackingPolicy extends Annotation {} final class ChangeTrackingPolicy implements Annotation {
/** @var string */
public $value;
}
/** /**
* @Annotation * @Annotation
* @Target("PROPERTY") * @Target("PROPERTY")
*/ */
final class OrderBy extends Annotation {} final class OrderBy implements Annotation {
/** @var array<string> */
public $value;
}
/** /**
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class NamedQueries extends Annotation {} final class NamedQueries implements Annotation {
/** @var array<Doctrine\ORM\Mapping\NamedQuery> */
public $value;
}
/** /**
* @Annotation * @Annotation
* @Target("ANNOTATION") * @Target("ANNOTATION")
*/ */
final class NamedQuery extends Annotation { final class NamedQuery implements Annotation {
/** @var string */
public $name; public $name;
/** @var string */
public $query; public $query;
} }
@ -261,46 +344,46 @@ final class NamedQuery extends Annotation {
* @Annotation * @Annotation
* @Target("CLASS") * @Target("CLASS")
*/ */
final class HasLifecycleCallbacks extends Annotation {} final class HasLifecycleCallbacks implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PrePersist extends Annotation {} final class PrePersist implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostPersist extends Annotation {} final class PostPersist implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PreUpdate extends Annotation {} final class PreUpdate implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostUpdate extends Annotation {} final class PostUpdate implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PreRemove extends Annotation {} final class PreRemove implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostRemove extends Annotation {} final class PostRemove implements Annotation {}
/** /**
* @Annotation * @Annotation
* @Target("METHOD") * @Target("METHOD")
*/ */
final class PostLoad extends Annotation {} final class PostLoad implements Annotation {}

@ -0,0 +1,176 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\MappingException;
/**
* XmlDriver that additionally looks for mapping information in a global file.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @license MIT
*/
class SimplifiedXmlDriver extends XmlDriver
{
protected $_prefixes = array();
protected $_globalBasename;
protected $_classCache;
protected $_fileExtension = '.orm.xml';
public function __construct($prefixes)
{
$this->addNamespacePrefixes($prefixes);
}
public function setGlobalBasename($file)
{
$this->_globalBasename = $file;
}
public function getGlobalBasename()
{
return $this->_globalBasename;
}
public function addNamespacePrefixes($prefixes)
{
$this->_prefixes = array_merge($this->_prefixes, $prefixes);
$this->addPaths(array_flip($prefixes));
}
public function getNamespacePrefixes()
{
return $this->_prefixes;
}
public function isTransient($className)
{
if (null === $this->_classCache) {
$this->initialize();
}
// The mapping is defined in the global mapping file
if (isset($this->_classCache[$className])) {
return false;
}
try {
$this->_findMappingFile($className);
return false;
} catch (MappingException $e) {
return true;
}
}
public function getAllClassNames()
{
if (null === $this->_classCache) {
$this->initialize();
}
$classes = array();
if ($this->_paths) {
foreach ((array) $this->_paths as $path) {
if (!is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
$fileName = $file->getBasename($this->_fileExtension);
if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
continue;
}
// NOTE: All files found here means classes are not transient!
if (isset($this->_prefixes[$path])) {
$classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
} else {
$classes[] = str_replace('.', '\\', $fileName);
}
}
}
}
return array_merge($classes, array_keys($this->_classCache));
}
public function getElement($className)
{
if (null === $this->_classCache) {
$this->initialize();
}
if (!isset($this->_classCache[$className])) {
$this->_classCache[$className] = parent::getElement($className);
}
return $this->_classCache[$className];
}
protected function initialize()
{
$this->_classCache = array();
if (null !== $this->_globalBasename) {
foreach ($this->_paths as $path) {
if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
$this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
}
}
}
}
protected function _findMappingFile($className)
{
$defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
foreach ($this->_paths as $path) {
if (!isset($this->_prefixes[$path])) {
if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
return $path.DIRECTORY_SEPARATOR.$defaultFileName;
}
continue;
}
$prefix = $this->_prefixes[$path];
if (0 !== strpos($className, $prefix.'\\')) {
continue;
}
$filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
if (is_file($filename)) {
return $filename;
}
throw MappingException::mappingFileNotFound($className, $filename);
}
throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
}
}

@ -0,0 +1,182 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\MappingException;
/**
* YamlDriver that additionally looks for mapping information in a global file.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @license MIT
*/
class SimplifiedYamlDriver extends YamlDriver
{
protected $_prefixes = array();
protected $_globalBasename;
protected $_classCache;
protected $_fileExtension = '.orm.yml';
public function __construct($prefixes)
{
$this->addNamespacePrefixes($prefixes);
}
public function setGlobalBasename($file)
{
$this->_globalBasename = $file;
}
public function getGlobalBasename()
{
return $this->_globalBasename;
}
public function addNamespacePrefixes($prefixes)
{
$this->_prefixes = array_merge($this->_prefixes, $prefixes);
$this->addPaths(array_flip($prefixes));
}
public function addNamespacePrefix($prefix, $path)
{
$this->_prefixes[$path] = $prefix;
}
public function getNamespacePrefixes()
{
return $this->_prefixes;
}
public function isTransient($className)
{
if (null === $this->_classCache) {
$this->initialize();
}
// The mapping is defined in the global mapping file
if (isset($this->_classCache[$className])) {
return false;
}
try {
$this->_findMappingFile($className);
return false;
} catch (MappingException $e) {
return true;
}
}
public function getAllClassNames()
{
if (null === $this->_classCache) {
$this->initialize();
}
$classes = array();
if ($this->_paths) {
foreach ((array) $this->_paths as $path) {
if (!is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
$fileName = $file->getBasename($this->_fileExtension);
if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) {
continue;
}
// NOTE: All files found here means classes are not transient!
if (isset($this->_prefixes[$path])) {
$classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName);
} else {
$classes[] = str_replace('.', '\\', $fileName);
}
}
}
}
return array_merge($classes, array_keys($this->_classCache));
}
public function getElement($className)
{
if (null === $this->_classCache) {
$this->initialize();
}
if (!isset($this->_classCache[$className])) {
$this->_classCache[$className] = parent::getElement($className);
}
return $this->_classCache[$className];
}
protected function initialize()
{
$this->_classCache = array();
if (null !== $this->_globalBasename) {
foreach ($this->_paths as $path) {
if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) {
$this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file));
}
}
}
}
protected function _findMappingFile($className)
{
$defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension;
foreach ($this->_paths as $path) {
if (!isset($this->_prefixes[$path])) {
if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) {
return $path.DIRECTORY_SEPARATOR.$defaultFileName;
}
continue;
}
$prefix = $this->_prefixes[$path];
if (0 !== strpos($className, $prefix.'\\')) {
continue;
}
$filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension;
if (is_file($filename)) {
return $filename;
}
throw MappingException::mappingFileNotFound($className, $filename);
}
throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension);
}
}

@ -52,13 +52,16 @@ class XmlDriver extends AbstractFileDriver
$xmlRoot = $this->getElement($className); $xmlRoot = $this->getElement($className);
if ($xmlRoot->getName() == 'entity') { if ($xmlRoot->getName() == 'entity') {
$metadata->setCustomRepositoryClass( if (isset($xmlRoot['repository-class'])) {
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null $metadata->setCustomRepositoryClass((string)$xmlRoot['repository-class']);
); }
if (isset($xmlRoot['read-only']) && $xmlRoot['read-only'] == "true") { if (isset($xmlRoot['read-only']) && $xmlRoot['read-only'] == "true") {
$metadata->markReadOnly(); $metadata->markReadOnly();
} }
} else if ($xmlRoot->getName() == 'mapped-superclass') { } else if ($xmlRoot->getName() == 'mapped-superclass') {
$metadata->setCustomRepositoryClass(
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
);
$metadata->isMappedSuperclass = true; $metadata->isMappedSuperclass = true;
} else { } else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);

@ -46,13 +46,16 @@ class YamlDriver extends AbstractFileDriver
$element = $this->getElement($className); $element = $this->getElement($className);
if ($element['type'] == 'entity') { if ($element['type'] == 'entity') {
$metadata->setCustomRepositoryClass( if (isset($element['repositoryClass'])) {
isset($element['repositoryClass']) ? $element['repositoryClass'] : null $metadata->setCustomRepositoryClass($element['repositoryClass']);
); }
if (isset($element['readOnly']) && $element['readOnly'] == true) { if (isset($element['readOnly']) && $element['readOnly'] == true) {
$metadata->markReadOnly(); $metadata->markReadOnly();
} }
} else if ($element['type'] == 'mappedSuperclass') { } else if ($element['type'] == 'mappedSuperclass') {
$metadata->setCustomRepositoryClass(
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
);
$metadata->isMappedSuperclass = true; $metadata->isMappedSuperclass = true;
} else { } else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);

@ -293,4 +293,9 @@ class MappingException extends \Doctrine\ORM\ORMException
"to avoid this exception from occuring." "to avoid this exception from occuring."
); );
} }
public static function lifecycleCallbackMethodNotFound($className, $methodName)
{
return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback.");
}
} }

@ -46,15 +46,14 @@ class ORMException extends Exception
); );
} }
public static function entityMissingAssignedId($entity) public static function entityMissingAssignedIdForField($entity, $field)
{ {
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " . return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " .
"The identifier generation strategy for this entity requires the ID field to be populated before ". "The identifier generation strategy for this entity requires the ID field to be populated before ".
"EntityManager#persist() is called. If you want automatically generated identifiers instead " . "EntityManager#persist() is called. If you want automatically generated identifiers instead " .
"you need to adjust the metadata mapping accordingly." "you need to adjust the metadata mapping accordingly."
); );
} }
public static function unrecognizedField($field) public static function unrecognizedField($field)
{ {
return new self("Unrecognized field: $field"); return new self("Unrecognized field: $field");
@ -130,4 +129,10 @@ class ORMException extends Exception
"Unknown Entity namespace alias '$entityNamespaceAlias'." "Unknown Entity namespace alias '$entityNamespaceAlias'."
); );
} }
public static function invalidEntityRepository($className)
{
return new self("Invalid repository class '".$className."'. ".
"it must be a Doctrine\ORM\EntityRepository.");
}
} }

@ -65,9 +65,11 @@ abstract class AbstractCollectionPersister
public function delete(PersistentCollection $coll) public function delete(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
if ( ! $mapping['isOwningSide']) { if ( ! $mapping['isOwningSide']) {
return; // ignore inverse side return; // ignore inverse side
} }
$sql = $this->_getDeleteSQL($coll); $sql = $this->_getDeleteSQL($coll);
$this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll)); $this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll));
} }
@ -96,9 +98,11 @@ abstract class AbstractCollectionPersister
public function update(PersistentCollection $coll) public function update(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
if ( ! $mapping['isOwningSide']) { if ( ! $mapping['isOwningSide']) {
return; // ignore inverse side return; // ignore inverse side
} }
$this->deleteRows($coll); $this->deleteRows($coll);
//$this->updateRows($coll); //$this->updateRows($coll);
$this->insertRows($coll); $this->insertRows($coll);
@ -108,6 +112,7 @@ abstract class AbstractCollectionPersister
{ {
$deleteDiff = $coll->getDeleteDiff(); $deleteDiff = $coll->getDeleteDiff();
$sql = $this->_getDeleteRowSQL($coll); $sql = $this->_getDeleteRowSQL($coll);
foreach ($deleteDiff as $element) { foreach ($deleteDiff as $element) {
$this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element)); $this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element));
} }
@ -120,6 +125,7 @@ abstract class AbstractCollectionPersister
{ {
$insertDiff = $coll->getInsertDiff(); $insertDiff = $coll->getInsertDiff();
$sql = $this->_getInsertRowSQL($coll); $sql = $this->_getInsertRowSQL($coll);
foreach ($insertDiff as $element) { foreach ($insertDiff as $element) {
$this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element)); $this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element));
} }

@ -39,10 +39,12 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
protected function _prepareInsertData($entity) protected function _prepareInsertData($entity)
{ {
$data = parent::_prepareInsertData($entity); $data = parent::_prepareInsertData($entity);
// Populate the discriminator column // Populate the discriminator column
$discColumn = $this->_class->discriminatorColumn; $discColumn = $this->_class->discriminatorColumn;
$this->_columnTypes[$discColumn['name']] = $discColumn['type']; $this->_columnTypes[$discColumn['name']] = $discColumn['type'];
$data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue; $data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue;
return $data; return $data;
} }
@ -63,7 +65,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
return "$sql AS $columnAlias"; return $sql . ' AS ' . $columnAlias;
} }
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
@ -72,6 +74,6 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
return $tableAlias . ".$joinColumnName AS $columnAlias"; return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
} }
} }

@ -222,13 +222,14 @@ class BasicEntityPersister
$isPostInsertId = $idGen->isPostInsertGenerator(); $isPostInsertId = $idGen->isPostInsertGenerator();
$stmt = $this->_conn->prepare($this->_getInsertSQL()); $stmt = $this->_conn->prepare($this->_getInsertSQL());
$tableName = $this->_class->table['name']; $tableName = $this->_class->getTableName();
foreach ($this->_queuedInserts as $entity) { foreach ($this->_queuedInserts as $entity) {
$insertData = $this->_prepareInsertData($entity); $insertData = $this->_prepareInsertData($entity);
if (isset($insertData[$tableName])) { if (isset($insertData[$tableName])) {
$paramIndex = 1; $paramIndex = 1;
foreach ($insertData[$tableName] as $column => $value) { foreach ($insertData[$tableName] as $column => $value) {
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]);
} }
@ -279,10 +280,13 @@ class BasicEntityPersister
{ {
$versionField = $versionedClass->versionField; $versionField = $versionedClass->versionField;
$identifier = $versionedClass->getIdentifierColumnNames(); $identifier = $versionedClass->getIdentifierColumnNames();
$versionFieldColumnName = $versionedClass->getColumnName($versionField);
$versionFieldColumnName = $versionedClass->getQuotedColumnName($versionField, $this->_platform);
//FIXME: Order with composite keys might not be correct //FIXME: Order with composite keys might not be correct
$sql = "SELECT " . $versionFieldColumnName . " FROM " . $versionedClass->getQuotedTableName($this->_platform) $sql = 'SELECT ' . $versionFieldColumnName
. " WHERE " . implode(' = ? AND ', $identifier) . " = ?"; . ' FROM ' . $versionedClass->getQuotedTableName($this->_platform)
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
$value = $this->_conn->fetchColumn($sql, array_values((array)$id)); $value = $this->_conn->fetchColumn($sql, array_values((array)$id));
return Type::getType($versionedClass->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform); return Type::getType($versionedClass->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform);
@ -305,7 +309,8 @@ class BasicEntityPersister
public function update($entity) public function update($entity)
{ {
$updateData = $this->_prepareUpdateData($entity); $updateData = $this->_prepareUpdateData($entity);
$tableName = $this->_class->table['name']; $tableName = $this->_class->getTableName();
if (isset($updateData[$tableName]) && $updateData[$tableName]) { if (isset($updateData[$tableName]) && $updateData[$tableName]) {
$this->_updateTable( $this->_updateTable(
$entity, $this->_class->getQuotedTableName($this->_platform), $entity, $this->_class->getQuotedTableName($this->_platform),
@ -333,17 +338,17 @@ class BasicEntityPersister
$set = $params = $types = array(); $set = $params = $types = array();
foreach ($updateData as $columnName => $value) { foreach ($updateData as $columnName => $value) {
if (isset($this->_class->fieldNames[$columnName])) { $set[] = (isset($this->_class->fieldNames[$columnName]))
$set[] = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'; ? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'
} else { : $columnName . ' = ?';
$set[] = $columnName . ' = ?';
}
$params[] = $value; $params[] = $value;
$types[] = $this->_columnTypes[$columnName]; $types[] = $this->_columnTypes[$columnName];
} }
$where = array(); $where = array();
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
foreach ($this->_class->identifier as $idField) { foreach ($this->_class->identifier as $idField) {
if (isset($this->_class->associationMappings[$idField])) { if (isset($this->_class->associationMappings[$idField])) {
$targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']);
@ -361,17 +366,20 @@ class BasicEntityPersister
$versionField = $this->_class->versionField; $versionField = $this->_class->versionField;
$versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionFieldType = $this->_class->fieldMappings[$versionField]['type'];
$versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform);
if ($versionFieldType == Type::INTEGER) { if ($versionFieldType == Type::INTEGER) {
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
} else if ($versionFieldType == Type::DATETIME) { } else if ($versionFieldType == Type::DATETIME) {
$set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; $set[] = $versionColumn . ' = CURRENT_TIMESTAMP';
} }
$where[] = $versionColumn; $where[] = $versionColumn;
$params[] = $this->_class->reflFields[$versionField]->getValue($entity); $params[] = $this->_class->reflFields[$versionField]->getValue($entity);
$types[] = $this->_class->fieldMappings[$versionField]['type']; $types[] = $this->_class->fieldMappings[$versionField]['type'];
} }
$sql = "UPDATE $quotedTableName SET " . implode(', ', $set) $sql = 'UPDATE ' . $quotedTableName
. ' SET ' . implode(', ', $set)
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';
$result = $this->_conn->executeUpdate($sql, $params, $types); $result = $this->_conn->executeUpdate($sql, $params, $types);
@ -398,21 +406,29 @@ class BasicEntityPersister
$relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']); $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$mapping = $relatedClass->associationMappings[$mapping['mappedBy']]; $mapping = $relatedClass->associationMappings[$mapping['mappedBy']];
$keys = array_keys($mapping['relationToTargetKeyColumns']); $keys = array_keys($mapping['relationToTargetKeyColumns']);
if ($selfReferential) { if ($selfReferential) {
$otherKeys = array_keys($mapping['relationToSourceKeyColumns']); $otherKeys = array_keys($mapping['relationToSourceKeyColumns']);
} }
} else { } else {
$keys = array_keys($mapping['relationToSourceKeyColumns']); $keys = array_keys($mapping['relationToSourceKeyColumns']);
if ($selfReferential) { if ($selfReferential) {
$otherKeys = array_keys($mapping['relationToTargetKeyColumns']); $otherKeys = array_keys($mapping['relationToTargetKeyColumns']);
} }
} }
if ( ! isset($mapping['isOnDeleteCascade'])) { if ( ! isset($mapping['isOnDeleteCascade'])) {
$this->_conn->delete($mapping['joinTable']['name'], array_combine($keys, $identifier)); $this->_conn->delete(
$this->_class->getQuotedJoinTableName($mapping, $this->_platform),
array_combine($keys, $identifier)
);
if ($selfReferential) { if ($selfReferential) {
$this->_conn->delete($mapping['joinTable']['name'], array_combine($otherKeys, $identifier)); $this->_conn->delete(
$this->_class->getQuotedJoinTableName($mapping, $this->_platform),
array_combine($otherKeys, $identifier)
);
} }
} }
} }
@ -463,7 +479,7 @@ class BasicEntityPersister
$result = array(); $result = array();
$uow = $this->_em->getUnitOfWork(); $uow = $this->_em->getUnitOfWork();
if ($versioned = $this->_class->isVersioned) { if (($versioned = $this->_class->isVersioned) != false) {
$versionField = $this->_class->versionField; $versionField = $this->_class->versionField;
} }
@ -477,6 +493,7 @@ class BasicEntityPersister
if (isset($this->_class->associationMappings[$field])) { if (isset($this->_class->associationMappings[$field])) {
$assoc = $this->_class->associationMappings[$field]; $assoc = $this->_class->associationMappings[$field];
// Only owning side of x-1 associations can have a FK column. // Only owning side of x-1 associations can have a FK column.
if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
continue; continue;
@ -484,6 +501,7 @@ class BasicEntityPersister
if ($newVal !== null) { if ($newVal !== null) {
$oid = spl_object_hash($newVal); $oid = spl_object_hash($newVal);
if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
// The associated entity $newVal is not yet persisted, so we must // The associated entity $newVal is not yet persisted, so we must
// set $newVal = null, in order to insert a null value and schedule an // set $newVal = null, in order to insert a null value and schedule an
@ -510,6 +528,7 @@ class BasicEntityPersister
} else { } else {
$result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]];
} }
$this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn);
} }
} else { } else {
@ -518,6 +537,7 @@ class BasicEntityPersister
$result[$this->getOwningTable($field)][$columnName] = $newVal; $result[$this->getOwningTable($field)][$columnName] = $newVal;
} }
} }
return $result; return $result;
} }
@ -548,7 +568,7 @@ class BasicEntityPersister
*/ */
public function getOwningTable($fieldName) public function getOwningTable($fieldName)
{ {
return $this->_class->table['name']; return $this->_class->getTableName();
} }
/** /**
@ -560,12 +580,13 @@ class BasicEntityPersister
* @param $assoc The association that connects the entity to load to another entity, if any. * @param $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation. * @param array $hints Hints for entity creation.
* @param int $lockMode * @param int $lockMode
* @param int $limit Limit number of results
* @return object The loaded and managed entity instance or NULL if the entity can not be found. * @return object The loaded and managed entity instance or NULL if the entity can not be found.
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id? * @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/ */
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0) public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null)
{ {
$sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode); $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit);
list($params, $types) = $this->expandParameters($criteria); list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types); $stmt = $this->_conn->executeQuery($sql, $params, $types);
@ -574,12 +595,9 @@ class BasicEntityPersister
$hints[Query::HINT_REFRESH_ENTITY] = $entity; $hints[Query::HINT_REFRESH_ENTITY] = $entity;
} }
if ($this->_selectJoinSql) { $hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
} else {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
}
$entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints);
return $entities ? $entities[0] : null; return $entities ? $entities[0] : null;
} }
@ -596,7 +614,7 @@ class BasicEntityPersister
*/ */
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()) public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{ {
if ($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) { if (($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) {
return $foundEntity; return $foundEntity;
} }
@ -608,14 +626,17 @@ class BasicEntityPersister
// Mark inverse side as fetched in the hints, otherwise the UoW would // Mark inverse side as fetched in the hints, otherwise the UoW would
// try to load it in a separate query (remember: to-one inverse sides can not be lazy). // try to load it in a separate query (remember: to-one inverse sides can not be lazy).
$hints = array(); $hints = array();
if ($isInverseSingleValued) { if ($isInverseSingleValued) {
$hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; $hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true;
if ($targetClass->subClasses) { if ($targetClass->subClasses) {
foreach ($targetClass->subClasses as $targetSubclassName) { foreach ($targetClass->subClasses as $targetSubclassName) {
$hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true; $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true;
} }
} }
} }
/* cascade read-only status /* cascade read-only status
if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) {
$hints[Query::HINT_READ_ONLY] = true; $hints[Query::HINT_READ_ONLY] = true;
@ -631,19 +652,21 @@ class BasicEntityPersister
} else { } else {
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
$owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']);
// TRICKY: since the association is specular source and target are flipped // TRICKY: since the association is specular source and target are flipped
foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
// unset the old value and set the new sql aliased value here. By definition
// unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method.
$identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
unset($identifier[$targetKeyColumn]);
} else {
throw MappingException::joinColumnMustPointToMappedField( throw MappingException::joinColumnMustPointToMappedField(
$sourceClass->name, $sourceKeyColumn $sourceClass->name, $sourceKeyColumn
); );
} }
// unset the old value and set the new sql aliased value here. By definition
// unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method.
$identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
unset($identifier[$targetKeyColumn]);
} }
$targetEntity = $this->load($identifier, null, $assoc); $targetEntity = $this->load($identifier, null, $assoc);
@ -675,7 +698,9 @@ class BasicEntityPersister
if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) { if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) {
$this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity); $this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity);
} }
$evm = $this->_em->getEventManager(); $evm = $this->_em->getEventManager();
if ($evm->hasListeners(Events::postLoad)) { if ($evm->hasListeners(Events::postLoad)) {
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
} }
@ -697,11 +722,8 @@ class BasicEntityPersister
list($params, $types) = $this->expandParameters($criteria); list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types); $stmt = $this->_conn->executeQuery($sql, $params, $types);
if ($this->_selectJoinSql) { $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
} else {
$hydrator = $this->_em->newHydrator(Query::HYDRATE_SIMPLEOBJECT);
}
return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true));
} }
@ -717,6 +739,7 @@ class BasicEntityPersister
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{ {
$stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit);
return $this->loadArrayFromStatement($assoc, $stmt); return $this->loadArrayFromStatement($assoc, $stmt);
} }
@ -725,6 +748,7 @@ class BasicEntityPersister
* *
* @param array $assoc * @param array $assoc
* @param Doctrine\DBAL\Statement $stmt * @param Doctrine\DBAL\Statement $stmt
*
* @return array * @return array
*/ */
private function loadArrayFromStatement($assoc, $stmt) private function loadArrayFromStatement($assoc, $stmt)
@ -739,6 +763,7 @@ class BasicEntityPersister
} }
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
return $hydrator->hydrateAll($stmt, $rsm, $hints); return $hydrator->hydrateAll($stmt, $rsm, $hints);
} }
@ -748,6 +773,8 @@ class BasicEntityPersister
* @param array $assoc * @param array $assoc
* @param Doctrine\DBAL\Statement $stmt * @param Doctrine\DBAL\Statement $stmt
* @param PersistentCollection $coll * @param PersistentCollection $coll
*
* @return array
*/ */
private function loadCollectionFromStatement($assoc, $stmt, $coll) private function loadCollectionFromStatement($assoc, $stmt, $coll)
{ {
@ -761,7 +788,8 @@ class BasicEntityPersister
} }
$hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT);
$hydrator->hydrateAll($stmt, $rsm, $hints);
return $hydrator->hydrateAll($stmt, $rsm, $hints);
} }
/** /**
@ -777,6 +805,7 @@ class BasicEntityPersister
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{ {
$stmt = $this->getManyToManyStatement($assoc, $sourceEntity); $stmt = $this->getManyToManyStatement($assoc, $sourceEntity);
return $this->loadCollectionFromStatement($assoc, $stmt, $coll); return $this->loadCollectionFromStatement($assoc, $stmt, $coll);
} }
@ -784,15 +813,18 @@ class BasicEntityPersister
{ {
$criteria = array(); $criteria = array();
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
if ($assoc['isOwningSide']) { if ($assoc['isOwningSide']) {
$quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform); $quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform);
foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
if ($sourceClass->containsForeignIdentifier) { if ($sourceClass->containsForeignIdentifier) {
$field = $sourceClass->getFieldForColumn($sourceKeyColumn); $field = $sourceClass->getFieldForColumn($sourceKeyColumn);
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
if (isset($sourceClass->associationMappings[$field])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
} }
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
@ -807,15 +839,18 @@ class BasicEntityPersister
} else { } else {
$owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']];
$quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform); $quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform);
// TRICKY: since the association is inverted source and target are flipped // TRICKY: since the association is inverted source and target are flipped
foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
if ($sourceClass->containsForeignIdentifier) { if ($sourceClass->containsForeignIdentifier) {
$field = $sourceClass->getFieldForColumn($sourceKeyColumn); $field = $sourceClass->getFieldForColumn($sourceKeyColumn);
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
if (isset($sourceClass->associationMappings[$field])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
} }
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value;
} else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
$criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
@ -829,6 +864,7 @@ class BasicEntityPersister
$sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset);
list($params, $types) = $this->expandParameters($criteria); list($params, $types) = $this->expandParameters($criteria);
return $this->_conn->executeQuery($sql, $params, $types); return $this->_conn->executeQuery($sql, $params, $types);
} }
@ -847,15 +883,14 @@ class BasicEntityPersister
*/ */
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{ {
$joinSql = $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ? $joinSql = ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
$this->_getSelectManyToManyJoinSQL($assoc) : '';
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : '';
$lockSql = ''; $lockSql = '';
if ($lockMode == LockMode::PESSIMISTIC_READ) { if ($lockMode == LockMode::PESSIMISTIC_READ) {
$lockSql = ' ' . $this->_platform->getReadLockSql(); $lockSql = ' ' . $this->_platform->getReadLockSql();
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
@ -882,6 +917,7 @@ class BasicEntityPersister
protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) protected final function _getOrderBySQL(array $orderBy, $baseTableAlias)
{ {
$orderBySql = ''; $orderBySql = '';
foreach ($orderBy as $fieldName => $orientation) { foreach ($orderBy as $fieldName => $orientation) {
if ( ! isset($this->_class->fieldMappings[$fieldName])) { if ( ! isset($this->_class->fieldMappings[$fieldName])) {
throw ORMException::unrecognizedField($fieldName); throw ORMException::unrecognizedField($fieldName);
@ -892,6 +928,7 @@ class BasicEntityPersister
: $baseTableAlias; : $baseTableAlias;
$columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform);
$orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $orderBySql ? ', ' : ' ORDER BY ';
$orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation;
} }
@ -924,20 +961,25 @@ class BasicEntityPersister
// Add regular columns to select list // Add regular columns to select list
foreach ($this->_class->fieldNames as $field) { foreach ($this->_class->fieldNames as $field) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnList .= $this->_getSelectColumnSQL($field, $this->_class); $columnList .= $this->_getSelectColumnSQL($field, $this->_class);
} }
$this->_selectJoinSql = ''; $this->_selectJoinSql = '';
$eagerAliasCounter = 0; $eagerAliasCounter = 0;
foreach ($this->_class->associationMappings as $assocField => $assoc) { foreach ($this->_class->associationMappings as $assocField => $assoc) {
$assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class); $assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class);
if ($assocColumnSQL) { if ($assocColumnSQL) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnList .= $assocColumnSQL; $columnList .= $assocColumnSQL;
} }
if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) { if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) {
continue; // now this is why you shouldn't use inheritance continue; // now this is why you shouldn't use inheritance
} }
@ -947,41 +989,48 @@ class BasicEntityPersister
foreach ($eagerEntity->fieldNames AS $field) { foreach ($eagerEntity->fieldNames AS $field) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias); $columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias);
} }
foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) { foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) {
$assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias); $assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias);
if ($assoc2ColumnSQL) { if ($assoc2ColumnSQL) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnList .= $assoc2ColumnSQL; $columnList .= $assoc2ColumnSQL;
} }
} }
$this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable. $this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable.
$first = true; $first = true;
if ($assoc['isOwningSide']) { if ($assoc['isOwningSide']) {
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
if (!$first) { if ( ! $first) {
$this->_selectJoinSql .= ' AND '; $this->_selectJoinSql .= ' AND ';
} }
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.'.$sourceCol.' = ' .
$this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.'.$targetCol.' '; $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
. $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol . ' ';
$first = false; $first = false;
} }
} else { } else {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
$this->_selectJoinSql .= ' ' . $eagerEntity->table['name'] . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON ';
foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
if (!$first) { if ( ! $first) {
$this->_selectJoinSql .= ' AND '; $this->_selectJoinSql .= ' AND ';
} }
$this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.'.$sourceCol.' = ' .
$this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' '; $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = '
. $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' ';
$first = false; $first = false;
} }
} }
@ -993,19 +1042,32 @@ class BasicEntityPersister
return $this->_selectColumnListSql; return $this->_selectColumnListSql;
} }
/**
* Gets the SQL join fragment used when selecting entities from an association.
*
* @param string $field
* @param array $assoc
* @param ClassMetadata $class
* @param string $alias
*
* @return string
*/
protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r')
{ {
$columnList = ''; $columnList = '';
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList) $columnList .= ', '; if ($columnList) $columnList .= ', ';
$columnAlias = $srcColumn . $this->_sqlAliasCounter++; $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, isset($assoc['id']) && $assoc['id'] === true); $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) )
. '.' . $srcColumn . ' AS ' . $resultColumnName;
$this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true);
} }
} }
return $columnList; return $columnList;
} }
@ -1027,23 +1089,22 @@ class BasicEntityPersister
} }
$joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform); $joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform);
$joinSql = ''; $joinSql = '';
foreach ($joinClauses as $joinTableColumn => $sourceColumn) { foreach ($joinClauses as $joinTableColumn => $sourceColumn) {
if ($joinSql != '') $joinSql .= ' AND '; if ($joinSql != '') $joinSql .= ' AND ';
if ($this->_class->containsForeignIdentifier && !isset($this->_class->fieldNames[$sourceColumn])) { if ($this->_class->containsForeignIdentifier && ! isset($this->_class->fieldNames[$sourceColumn])) {
$quotedColumn = $sourceColumn; // join columns cannot be quoted $quotedColumn = $sourceColumn; // join columns cannot be quoted
} else { } else {
$quotedColumn = $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform); $quotedColumn = $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform);
} }
$joinSql .= $this->_getSQLTableAlias($this->_class->name) . $joinSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $quotedColumn . ' = '
'.' . $quotedColumn . ' = '
. $joinTableName . '.' . $joinTableColumn; . $joinTableName . '.' . $joinTableColumn;
} }
return " INNER JOIN $joinTableName ON $joinSql"; return ' INNER JOIN ' . $joinTableName . ' ON ' . $joinSql;
} }
/** /**
@ -1056,6 +1117,7 @@ class BasicEntityPersister
if ($this->_insertSql === null) { if ($this->_insertSql === null) {
$insertSql = ''; $insertSql = '';
$columns = $this->_getInsertColumnList(); $columns = $this->_getInsertColumnList();
if (empty($columns)) { if (empty($columns)) {
$insertSql = $this->_platform->getEmptyIdentityInsertSQL( $insertSql = $this->_platform->getEmptyIdentityInsertSQL(
$this->_class->getQuotedTableName($this->_platform), $this->_class->getQuotedTableName($this->_platform),
@ -1066,11 +1128,12 @@ class BasicEntityPersister
$values = array_fill(0, count($columns), '?'); $values = array_fill(0, count($columns), '?');
$insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform)
. ' (' . implode(', ', $columns) . ') ' . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
. 'VALUES (' . implode(', ', $values) . ')';
} }
$this->_insertSql = $insertSql; $this->_insertSql = $insertSql;
} }
return $this->_insertSql; return $this->_insertSql;
} }
@ -1085,19 +1148,21 @@ class BasicEntityPersister
protected function _getInsertColumnList() protected function _getInsertColumnList()
{ {
$columns = array(); $columns = array();
foreach ($this->_class->reflFields as $name => $field) { foreach ($this->_class->reflFields as $name => $field) {
if ($this->_class->isVersioned && $this->_class->versionField == $name) { if ($this->_class->isVersioned && $this->_class->versionField == $name) {
continue; continue;
} }
if (isset($this->_class->associationMappings[$name])) { if (isset($this->_class->associationMappings[$name])) {
$assoc = $this->_class->associationMappings[$name]; $assoc = $this->_class->associationMappings[$name];
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
$columns[] = $sourceCol; $columns[] = $sourceCol;
} }
} }
} else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) {
$this->_class->identifier[0] != $name) {
$columns[] = $this->_class->getQuotedColumnName($name, $this->_platform); $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
} }
} }
@ -1116,11 +1181,13 @@ class BasicEntityPersister
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
{ {
$columnName = $class->columnNames[$field]; $columnName = $class->columnNames[$field];
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias)
. '.' . $class->getQuotedColumnName($field, $this->_platform);
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
$this->_rsm->addFieldResult($alias, $columnAlias, $field); $this->_rsm->addFieldResult($alias, $columnAlias, $field);
return "$sql AS $columnAlias"; return $sql . ' AS ' . $columnAlias;
} }
/** /**
@ -1133,15 +1200,17 @@ class BasicEntityPersister
protected function _getSQLTableAlias($className, $assocName = '') protected function _getSQLTableAlias($className, $assocName = '')
{ {
if ($assocName) { if ($assocName) {
$className .= '#'.$assocName; $className .= '#' . $assocName;
} }
if (isset($this->_sqlTableAliases[$className])) { if (isset($this->_sqlTableAliases[$className])) {
return $this->_sqlTableAliases[$className]; return $this->_sqlTableAliases[$className];
} }
$tableAlias = 't' . $this->_sqlAliasCounter++; $tableAlias = 't' . $this->_sqlAliasCounter++;
$this->_sqlTableAliases[$className] = $tableAlias; $this->_sqlTableAliases[$className] = $tableAlias;
return $tableAlias; return $tableAlias;
} }
@ -1165,7 +1234,9 @@ class BasicEntityPersister
$sql = 'SELECT 1 ' $sql = 'SELECT 1 '
. $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode)
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
list($params, $types) = $this->expandParameters($criteria); list($params, $types) = $this->expandParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types); $stmt = $this->_conn->executeQuery($sql, $params, $types);
} }
@ -1194,28 +1265,26 @@ class BasicEntityPersister
protected function _getSelectConditionSQL(array $criteria, $assoc = null) protected function _getSelectConditionSQL(array $criteria, $assoc = null)
{ {
$conditionSql = ''; $conditionSql = '';
foreach ($criteria as $field => $value) { foreach ($criteria as $field => $value) {
$conditionSql .= $conditionSql ? ' AND ' : ''; $conditionSql .= $conditionSql ? ' AND ' : '';
if (isset($this->_class->columnNames[$field])) { if (isset($this->_class->columnNames[$field])) {
if (isset($this->_class->fieldMappings[$field]['inherited'])) { $className = (isset($this->_class->fieldMappings[$field]['inherited']))
$conditionSql .= $this->_getSQLTableAlias($this->_class->fieldMappings[$field]['inherited']) . '.'; ? $this->_class->fieldMappings[$field]['inherited']
} else { : $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
} $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
$conditionSql .= $this->_class->getQuotedColumnName($field, $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);
} }
if (isset($this->_class->associationMappings[$field]['inherited'])) { $className = (isset($this->_class->associationMappings[$field]['inherited']))
$conditionSql .= $this->_getSQLTableAlias($this->_class->associationMappings[$field]['inherited']) . '.'; ? $this->_class->associationMappings[$field]['inherited']
} else { : $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
}
$conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name']; $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
} else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { } else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) {
// very careless developers could potentially open up this normally hidden api for userland attacks, // very careless developers could potentially open up this normally hidden api for userland attacks,
// therefore checking for spaces and function calls which are not allowed. // therefore checking for spaces and function calls which are not allowed.
@ -1243,6 +1312,7 @@ class BasicEntityPersister
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{ {
$stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit);
return $this->loadArrayFromStatement($assoc, $stmt); return $this->loadArrayFromStatement($assoc, $stmt);
} }
@ -1258,7 +1328,8 @@ class BasicEntityPersister
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
{ {
$stmt = $this->getOneToManyStatement($assoc, $sourceEntity); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity);
$this->loadCollectionFromStatement($assoc, $stmt, $coll);
return $this->loadCollectionFromStatement($assoc, $stmt, $coll);
} }
/** /**
@ -1276,18 +1347,18 @@ class BasicEntityPersister
$owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']]; $owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']];
$sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
$tableAlias = isset($owningAssoc['inherited']) ? $tableAlias = $this->_getSQLTableAlias(isset($owningAssoc['inherited']) ? $owningAssoc['inherited'] : $this->_class->name);
$this->_getSQLTableAlias($owningAssoc['inherited'])
: $this->_getSQLTableAlias($this->_class->name);
foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
if ($sourceClass->containsForeignIdentifier) { if ($sourceClass->containsForeignIdentifier) {
$field = $sourceClass->getFieldForColumn($sourceKeyColumn); $field = $sourceClass->getFieldForColumn($sourceKeyColumn);
$value = $sourceClass->reflFields[$field]->getValue($sourceEntity); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity);
if (isset($sourceClass->associationMappings[$field])) { if (isset($sourceClass->associationMappings[$field])) {
$value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
$value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]];
} }
$criteria[$tableAlias . "." . $targetKeyColumn] = $value; $criteria[$tableAlias . "." . $targetKeyColumn] = $value;
} else { } else {
$criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
@ -1416,14 +1487,17 @@ class BasicEntityPersister
public function exists($entity, array $extraConditions = array()) public function exists($entity, array $extraConditions = array())
{ {
$criteria = $this->_class->getIdentifierValues($entity); $criteria = $this->_class->getIdentifierValues($entity);
if ($extraConditions) { if ($extraConditions) {
$criteria = array_merge($criteria, $extraConditions); $criteria = array_merge($criteria, $extraConditions);
} }
$sql = 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform) $sql = 'SELECT 1'
. ' ' . $this->_getSQLTableAlias($this->_class->name) . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name)
. ' WHERE ' . $this->_getSelectConditionSQL($criteria); . ' WHERE ' . $this->_getSelectConditionSQL($criteria);
return (bool) $this->_conn->fetchColumn($sql, array_values($criteria)); list($params, $types) = $this->expandParameters($criteria);
return (bool) $this->_conn->fetchColumn($sql, $params);
} }
} }

@ -22,6 +22,7 @@ namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\ORMException, use Doctrine\ORM\ORMException,
Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\DBAL\LockMode, Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\ResultSetMapping; Doctrine\ORM\Query\ResultSetMapping;
/** /**
@ -55,11 +56,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/ */
protected function _getDiscriminatorColumnTableName() protected function _getDiscriminatorColumnTableName()
{ {
if ($this->_class->name == $this->_class->rootEntityName) { $class = ($this->_class->name !== $this->_class->rootEntityName)
return $this->_class->table['name']; ? $this->_em->getClassMetadata($this->_class->rootEntityName)
} else { : $this->_class;
return $this->_em->getClassMetadata($this->_class->rootEntityName)->table['name'];
} return $class->getTableName();
} }
/** /**
@ -72,8 +73,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
{ {
if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) {
$definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited'];
return $this->_em->getClassMetadata($definingClassName); return $this->_em->getClassMetadata($definingClassName);
} }
return $this->_class; return $this->_class;
} }
@ -86,7 +89,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/ */
public function getOwningTable($fieldName) public function getOwningTable($fieldName)
{ {
if (!isset($this->_owningTableMap[$fieldName])) { if (isset($this->_owningTableMap[$fieldName])) {
return $this->_owningTableMap[$fieldName];
}
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
$cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
@ -94,11 +100,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
} else { } else {
$cm = $this->_class; $cm = $this->_class;
} }
$this->_owningTableMap[$fieldName] = $cm->table['name'];
$this->_quotedTableMap[$cm->table['name']] = $cm->getQuotedTableName($this->_platform);
}
return $this->_owningTableMap[$fieldName]; $tableName = $cm->getTableName();
$this->_owningTableMap[$fieldName] = $tableName;
$this->_quotedTableMap[$tableName] = $cm->getQuotedTableName($this->_platform);
return $tableName;
} }
/** /**
@ -115,20 +123,22 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$isPostInsertId = $idGen->isPostInsertGenerator(); $isPostInsertId = $idGen->isPostInsertGenerator();
// Prepare statement for the root table // Prepare statement for the root table
$rootClass = $this->_class->name == $this->_class->rootEntityName ? $rootClass = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class;
$this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName);
$rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name);
$rootTableName = $rootClass->table['name']; $rootTableName = $rootClass->getTableName();
$rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL()); $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL());
// Prepare statements for sub tables. // Prepare statements for sub tables.
$subTableStmts = array(); $subTableStmts = array();
if ($rootClass !== $this->_class) { if ($rootClass !== $this->_class) {
$subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->_getInsertSQL()); $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL());
} }
foreach ($this->_class->parentClasses as $parentClassName) { foreach ($this->_class->parentClasses as $parentClassName) {
$parentClass = $this->_em->getClassMetadata($parentClassName); $parentClass = $this->_em->getClassMetadata($parentClassName);
$parentTableName = $parentClass->table['name']; $parentTableName = $parentClass->getTableName();
if ($parentClass !== $rootClass) { if ($parentClass !== $rootClass) {
$parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName);
$subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL());
@ -143,9 +153,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Execute insert on root table // Execute insert on root table
$paramIndex = 1; $paramIndex = 1;
foreach ($insertData[$rootTableName] as $columnName => $value) { foreach ($insertData[$rootTableName] as $columnName => $value) {
$rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
} }
$rootTableStmt->execute(); $rootTableStmt->execute();
if ($isPostInsertId) { if ($isPostInsertId) {
@ -160,17 +172,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
foreach ($subTableStmts as $tableName => $stmt) { foreach ($subTableStmts as $tableName => $stmt) {
$data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array();
$paramIndex = 1; $paramIndex = 1;
foreach ((array) $id as $idVal) {
$stmt->bindValue($paramIndex++, $idVal); foreach ((array) $id as $idName => $idVal) {
$type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING;
$stmt->bindValue($paramIndex++, $idVal, $type);
} }
foreach ($data as $columnName => $value) { foreach ($data as $columnName => $value) {
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
} }
$stmt->execute(); $stmt->execute();
} }
} }
$rootTableStmt->closeCursor(); $rootTableStmt->closeCursor();
foreach ($subTableStmts as $stmt) { foreach ($subTableStmts as $stmt) {
$stmt->closeCursor(); $stmt->closeCursor();
} }
@ -191,15 +209,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
{ {
$updateData = $this->_prepareUpdateData($entity); $updateData = $this->_prepareUpdateData($entity);
if ($isVersioned = $this->_class->isVersioned) { if (($isVersioned = $this->_class->isVersioned) != false) {
$versionedClass = $this->_getVersionedClassMetadata(); $versionedClass = $this->_getVersionedClassMetadata();
$versionedTable = $versionedClass->table['name']; $versionedTable = $versionedClass->getTableName();
} }
if ($updateData) { if ($updateData) {
foreach ($updateData as $tableName => $data) { foreach ($updateData as $tableName => $data) {
$this->_updateTable($entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName); $this->_updateTable(
$entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName
);
} }
// Make sure the table with the version column is updated even if no columns on that // Make sure the table with the version column is updated even if no columns on that
// table were affected. // table were affected.
if ($isVersioned && ! isset($updateData[$versionedTable])) { if ($isVersioned && ! isset($updateData[$versionedTable])) {
@ -224,13 +245,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// If the database platform supports FKs, just // If the database platform supports FKs, just
// delete the row from the root table. Cascades do the rest. // delete the row from the root table. Cascades do the rest.
if ($this->_platform->supportsForeignKeyConstraints()) { if ($this->_platform->supportsForeignKeyConstraints()) {
$this->_conn->delete($this->_em->getClassMetadata($this->_class->rootEntityName) $this->_conn->delete(
->getQuotedTableName($this->_platform), $id); $this->_em->getClassMetadata($this->_class->rootEntityName)->getQuotedTableName($this->_platform), $id
);
} else { } else {
// Delete from all tables individually, starting from this class' table up to the root table. // Delete from all tables individually, starting from this class' table up to the root table.
$this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id); $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
foreach ($this->_class->parentClasses as $parentClass) { foreach ($this->_class->parentClasses as $parentClass) {
$this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id); $this->_conn->delete(
$this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id
);
} }
} }
} }
@ -251,23 +276,27 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Add regular columns // Add regular columns
$columnList = ''; $columnList = '';
foreach ($this->_class->fieldMappings as $fieldName => $mapping) { foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
if ($columnList != '') $columnList .= ', '; if ($columnList != '') $columnList .= ', ';
$columnList .= $this->_getSelectColumnSQL($fieldName,
isset($mapping['inherited']) ? $columnList .= $this->_getSelectColumnSQL(
$this->_em->getClassMetadata($mapping['inherited']) : $fieldName,
$this->_class); isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class
);
} }
// Add foreign key columns // Add foreign key columns
foreach ($this->_class->associationMappings as $assoc2) { foreach ($this->_class->associationMappings as $assoc2) {
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) {
$tableAlias = isset($assoc2['inherited']) ? $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias;
$this->_getSQLTableAlias($assoc2['inherited'])
: $baseTableAlias;
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList != '') $columnList .= ', '; if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
$columnList .= $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
); );
} }
@ -276,27 +305,27 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult). // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult).
$discrColumn = $this->_class->discriminatorColumn['name']; $discrColumn = $this->_class->discriminatorColumn['name'];
if ($this->_class->rootEntityName == $this->_class->name) { $tableAlias = ($this->_class->rootEntityName == $this->_class->name) ? $baseTableAlias : $this->_getSQLTableAlias($this->_class->rootEntityName);
$columnList .= ", $baseTableAlias.$discrColumn"; $columnList .= ', ' . $tableAlias . '.' . $discrColumn;
} else {
$columnList .= ', ' . $this->_getSQLTableAlias($this->_class->rootEntityName)
. ".$discrColumn";
}
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
} }
// INNER JOIN parent tables // INNER JOIN parent tables
$joinSql = ''; $joinSql = '';
foreach ($this->_class->parentClasses as $parentClassName) { foreach ($this->_class->parentClasses as $parentClassName) {
$parentClass = $this->_em->getClassMetadata($parentClassName); $parentClass = $this->_em->getClassMetadata($parentClassName);
$tableAlias = $this->_getSQLTableAlias($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName);
$joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($idColumns as $idColumn) { foreach ($idColumns as $idColumn) {
if ($first) $first = false; else $joinSql .= ' AND '; if ($first) $first = false; else $joinSql .= ' AND ';
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
} }
} }
@ -309,19 +338,20 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($this->_selectColumnListSql === null) { if ($this->_selectColumnListSql === null) {
// Add subclass columns // Add subclass columns
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
if (isset($mapping['inherited'])) { if (isset($mapping['inherited'])) continue;
continue;
}
$columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass);
} }
// Add join columns (foreign keys) // Add join columns (foreign keys)
foreach ($subClass->associationMappings as $assoc2) { foreach ($subClass->associationMappings as $assoc2) {
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) {
&& ! isset($assoc2['inherited'])) {
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList != '') $columnList .= ', '; if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
$columnList .= $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
); );
} }
@ -332,14 +362,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Add LEFT JOIN // Add LEFT JOIN
$joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($idColumns as $idColumn) { foreach ($idColumns as $idColumn) {
if ($first) $first = false; else $joinSql .= ' AND '; if ($first) $first = false; else $joinSql .= ' AND ';
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
} }
} }
$joinSql .= $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ? $joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
$this->_getSelectManyToManyJoinSQL($assoc) : '';
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc); $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
@ -351,6 +382,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
} }
$lockSql = ''; $lockSql = '';
if ($lockMode == LockMode::PESSIMISTIC_READ) { if ($lockMode == LockMode::PESSIMISTIC_READ) {
$lockSql = ' ' . $this->_platform->getReadLockSql(); $lockSql = ' ' . $this->_platform->getReadLockSql();
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
@ -376,13 +408,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// INNER JOIN parent tables // INNER JOIN parent tables
$joinSql = ''; $joinSql = '';
foreach ($this->_class->parentClasses as $parentClassName) { foreach ($this->_class->parentClasses as $parentClassName) {
$parentClass = $this->_em->getClassMetadata($parentClassName); $parentClass = $this->_em->getClassMetadata($parentClassName);
$tableAlias = $this->_getSQLTableAlias($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName);
$joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($idColumns as $idColumn) { foreach ($idColumns as $idColumn) {
if ($first) $first = false; else $joinSql .= ' AND '; if ($first) $first = false; else $joinSql .= ' AND ';
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
} }
} }

@ -40,9 +40,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
protected function _getDeleteRowSQL(PersistentCollection $coll) protected function _getDeleteRowSQL(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$joinTable = $mapping['joinTable']; $class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
$columns = $mapping['joinTableColumns'];
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . implode(' = ? AND ', $mapping['joinTableColumns']) . ' = ?';
} }
/** /**
@ -75,10 +76,11 @@ class ManyToManyPersister extends AbstractCollectionPersister
protected function _getInsertRowSQL(PersistentCollection $coll) protected function _getInsertRowSQL(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$joinTable = $mapping['joinTable'];
$columns = $mapping['joinTableColumns']; $columns = $mapping['joinTableColumns'];
return 'INSERT INTO ' . $joinTable['name'] . ' (' . implode(', ', $columns) . ')' $class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
return 'INSERT INTO ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
} }
/** /**
@ -150,13 +152,18 @@ class ManyToManyPersister extends AbstractCollectionPersister
protected function _getDeleteSQL(PersistentCollection $coll) protected function _getDeleteSQL(PersistentCollection $coll)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$class = $this->_em->getClassMetadata(get_class($coll->getOwner()));
$joinTable = $mapping['joinTable']; $joinTable = $mapping['joinTable'];
$whereClause = ''; $whereClause = '';
foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) { foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
if ($whereClause !== '') $whereClause .= ' AND '; if ($whereClause !== '') $whereClause .= ' AND ';
$whereClause .= "$relationColumn = ?";
$whereClause .= $relationColumn . ' = ?';
} }
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . $whereClause;
} }
/** /**
@ -171,8 +178,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
$params = array(); $params = array();
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$identifier = $this->_uow->getEntityIdentifier($coll->getOwner()); $identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
if (count($mapping['relationToSourceKeyColumns']) > 1) { if (count($mapping['relationToSourceKeyColumns']) > 1) {
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner())); $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) { foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]]; $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
} }
@ -194,30 +203,31 @@ class ManyToManyPersister extends AbstractCollectionPersister
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
if ($mapping['isOwningSide']) { if ($mapping['isOwningSide']) {
$joinTable = $mapping['joinTable'];
$joinColumns = $mapping['relationToSourceKeyColumns']; $joinColumns = $mapping['relationToSourceKeyColumns'];
} else { } else {
$mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']]; $mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']];
$joinTable = $mapping['joinTable'];
$joinColumns = $mapping['relationToTargetKeyColumns']; $joinColumns = $mapping['relationToTargetKeyColumns'];
} }
$whereClause = ''; $whereClause = '';
foreach ($mapping['joinTableColumns'] as $joinTableColumn) { foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($joinColumns[$joinTableColumn])) { if (isset($joinColumns[$joinTableColumn])) {
if ($whereClause !== '') { if ($whereClause !== '') {
$whereClause .= ' AND '; $whereClause .= ' AND ';
} }
$whereClause .= "$joinTableColumn = ?"; $whereClause .= "$joinTableColumn = ?";
if ($class->containsForeignIdentifier) { $params[] = ($class->containsForeignIdentifier)
$params[] = $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]; ? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
} else { : $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
$params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
} }
} }
}
$sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause; $sql = 'SELECT COUNT(*)'
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . $whereClause;
return $this->_conn->fetchColumn($sql, $params); return $this->_conn->fetchColumn($sql, $params);
} }
@ -231,9 +241,8 @@ class ManyToManyPersister extends AbstractCollectionPersister
public function slice(PersistentCollection $coll, $offset, $length = null) public function slice(PersistentCollection $coll, $offset, $length = null)
{ {
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
return $this->_em->getUnitOfWork()
->getEntityPersister($mapping['targetEntity']) return $this->_em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
} }
/** /**
@ -252,7 +261,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
$params = array(); $params = array();
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
if (!$mapping['isOwningSide']) { if ( ! $mapping['isOwningSide']) {
$sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']); $sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
$sourceId = $uow->getEntityIdentifier($element); $sourceId = $uow->getEntityIdentifier($element);
@ -265,36 +274,37 @@ class ManyToManyPersister extends AbstractCollectionPersister
$sourceId = $uow->getEntityIdentifier($coll->getOwner()); $sourceId = $uow->getEntityIdentifier($coll->getOwner());
$targetId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($element);
} }
$joinTable = $mapping['joinTable'];
$whereClause = ''; $whereClause = '';
foreach ($mapping['joinTableColumns'] as $joinTableColumn) { foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
if ($whereClause !== '') { if ($whereClause !== '') {
$whereClause .= ' AND '; $whereClause .= ' AND ';
} }
$whereClause .= "$joinTableColumn = ?";
if ($targetClass->containsForeignIdentifier) { $whereClause .= $joinTableColumn . ' = ?';
$params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
} else { $params[] = ($targetClass->containsForeignIdentifier)
$params[] = $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; ? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]
} : $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
} else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { } else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
if ($whereClause !== '') { if ($whereClause !== '') {
$whereClause .= ' AND '; $whereClause .= ' AND ';
} }
$whereClause .= "$joinTableColumn = ?";
if ($sourceClass->containsForeignIdentifier) { $whereClause .= $joinTableColumn . ' = ?';
$params[] = $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
} else {
$params[] = $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
}
}
$sql = 'SELECT 1 FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
return (bool)$this->_conn->fetchColumn($sql, $params); $params[] = ($sourceClass->containsForeignIdentifier)
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
}
$sql = 'SELECT 1'
. ' FROM ' . $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . $whereClause;
return (bool) $this->_conn->fetchColumn($sql, $params);
} }
} }

@ -35,38 +35,49 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
/** {@inheritdoc} */ /** {@inheritdoc} */
protected function _getDiscriminatorColumnTableName() protected function _getDiscriminatorColumnTableName()
{ {
return $this->_class->table['name']; return $this->_class->getTableName();
} }
/** {@inheritdoc} */ /** {@inheritdoc} */
protected function _getSelectColumnListSQL() protected function _getSelectColumnListSQL()
{ {
if ($this->_selectColumnListSql !== null) {
return $this->_selectColumnListSql;
}
$columnList = parent::_getSelectColumnListSQL(); $columnList = parent::_getSelectColumnListSQL();
// Append discriminator column // Append discriminator column
$discrColumn = $this->_class->discriminatorColumn['name']; $discrColumn = $this->_class->discriminatorColumn['name'];
$columnList .= ", $discrColumn"; $columnList .= ', ' . $discrColumn;
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
$tableAlias = $this->_getSQLTableAlias($rootClass->name); $tableAlias = $this->_getSQLTableAlias($rootClass->name);
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
// Append subclass columns // Append subclass columns
foreach ($this->_class->subClasses as $subClassName) { foreach ($this->_class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
// Regular columns // Regular columns
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
if ( ! isset($mapping['inherited'])) { if ( ! isset($mapping['inherited'])) {
$columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass);
} }
} }
// Foreign key columns // Foreign key columns
foreach ($subClass->associationMappings as $assoc) { foreach ($subClass->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($columnList != '') $columnList .= ', '; if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
$columnList .= $this->getSelectJoinColumnSQL(
$tableAlias,
$srcColumn,
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
); );
} }
@ -74,13 +85,15 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
} }
} }
return $columnList; $this->_selectColumnListSql = $columnList;
return $this->_selectColumnListSql;
} }
/** {@inheritdoc} */ /** {@inheritdoc} */
protected function _getInsertColumnList() protected function _getInsertColumnList()
{ {
$columns = parent::_getInsertColumnList(); $columns = parent::_getInsertColumnList();
// Add discriminator column to the INSERT SQL // Add discriminator column to the INSERT SQL
$columns[] = $this->_class->discriminatorColumn['name']; $columns[] = $this->_class->discriminatorColumn['name'];
@ -100,17 +113,20 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
// Append discriminator condition // Append discriminator condition
if ($conditionSql) $conditionSql .= ' AND '; if ($conditionSql) $conditionSql .= ' AND ';
$values = array(); $values = array();
if ($this->_class->discriminatorValue !== null) { // discriminators can be 0 if ($this->_class->discriminatorValue !== null) { // discriminators can be 0
$values[] = $this->_conn->quote($this->_class->discriminatorValue); $values[] = $this->_conn->quote($this->_class->discriminatorValue);
} }
$discrValues = array_flip($this->_class->discriminatorMap); $discrValues = array_flip($this->_class->discriminatorMap);
foreach ($this->_class->subClasses as $subclassName) { foreach ($this->_class->subClasses as $subclassName) {
$values[] = $this->_conn->quote($discrValues[$subclassName]); $values[] = $this->_conn->quote($discrValues[$subclassName]);
} }
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'
. $this->_class->discriminatorColumn['name'] $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
. ' IN (' . implode(', ', $values) . ')'; . ' IN (' . implode(', ', $values) . ')';
return $conditionSql; return $conditionSql;

@ -209,6 +209,11 @@ class ProxyFactory
$methods .= $parameterString . ')'; $methods .= $parameterString . ')';
$methods .= "\n" . ' {' . "\n"; $methods .= "\n" . ' {' . "\n";
if ($this->isShortIdentifierGetter($method, $class)) {
$methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
$methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . "\n";
$methods .= ' }' . "\n";
}
$methods .= ' $this->__load();' . "\n"; $methods .= ' $this->__load();' . "\n";
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
$methods .= "\n" . ' }' . "\n"; $methods .= "\n" . ' }' . "\n";
@ -218,6 +223,21 @@ class ProxyFactory
return $methods; return $methods;
} }
/**
* @param ReflectionMethod $method
* @param ClassMetadata $class
* @return bool
*/
private function isShortIdentifierGetter($method, $class)
{
return (
$method->getNumberOfParameters() == 0 &&
substr($method->getName(), 0, 3) == "get" &&
in_array(lcfirst(substr($method->getName(), 3)), $class->identifier, true) &&
(($method->getEndLine() - $method->getStartLine()) <= 4)
);
}
/** /**
* Generates the code for the __sleep method for a proxy class. * Generates the code for the __sleep method for a proxy class.
* *

@ -206,6 +206,7 @@ final class Query extends AbstractQuery
if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) { if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
$hash = $this->_getQueryCacheId(); $hash = $this->_getQueryCacheId();
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
if ($cached === false) { if ($cached === false) {
// Cache miss. // Cache miss.
$parser = new Parser($this); $parser = new Parser($this);
@ -219,6 +220,7 @@ final class Query extends AbstractQuery
$parser = new Parser($this); $parser = new Parser($this);
$this->_parserResult = $parser->parse(); $this->_parserResult = $parser->parse();
} }
$this->_state = self::STATE_CLEAN; $this->_state = self::STATE_CLEAN;
return $this->_parserResult; return $this->_parserResult;
@ -238,36 +240,42 @@ final class Query extends AbstractQuery
throw QueryException::invalidParameterNumber(); throw QueryException::invalidParameterNumber();
} }
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
if ($this->_resultSetMapping === null) {
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
}
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
}
/**
* Processes query parameter mappings
*
* @param array $paramMappings
* @return array
*/
private function processParameterMappings($paramMappings)
{
$sqlParams = $types = array(); $sqlParams = $types = array();
foreach ($this->_params as $key => $value) { foreach ($this->_params as $key => $value) {
if ( ! isset($paramMappings[$key])) { if ( ! isset($paramMappings[$key])) {
throw QueryException::unknownParameter($key); throw QueryException::unknownParameter($key);
} }
if (isset($this->_paramTypes[$key])) { if (isset($this->_paramTypes[$key])) {
foreach ($paramMappings[$key] as $position) { foreach ($paramMappings[$key] as $position) {
$types[$position] = $this->_paramTypes[$key]; $types[$position] = $this->_paramTypes[$key];
} }
} }
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$sqlPositions = $paramMappings[$key]; $sqlPositions = $paramMappings[$key];
$cSqlPos = count($sqlPositions); $value = array_values($this->processParameterValue($value));
$cIdValues = count($idValues); $countValue = count($value);
$idValues = array_values($idValues);
for ($i = 0; $i < $cSqlPos; $i++) { for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
$sqlParams[$sqlPositions[$i]] = $idValues[ ($i % $cIdValues) ]; $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
}
} else {
foreach ($paramMappings[$key] as $position) {
$sqlParams[$position] = $value;
}
} }
} }
@ -276,11 +284,40 @@ final class Query extends AbstractQuery
$sqlParams = array_values($sqlParams); $sqlParams = array_values($sqlParams);
} }
if ($this->_resultSetMapping === null) { return array($sqlParams, $types);
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
} }
return $executor->execute($this->_em->getConnection(), $sqlParams, $types); /**
* Process an individual parameter value
*
* @param mixed $value
* @return array
*/
private function processParameterValue($value)
{
switch (true) {
case is_array($value):
for ($i = 0, $l = count($value); $i < $l; $i++) {
$paramValue = $this->processParameterValue($value[$i]);
// TODO: What about Entities that have composite primary key?
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}
return array($value);
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)):
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
}
$class = $this->_em->getClassMetadata(get_class($value));
return array_values($class->getIdentifierValues($value));
default:
return array($value);
}
} }
/** /**

@ -0,0 +1,68 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
/**
* "IDENTITY" "(" SingleValuedAssociationPathExpression ")"
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.2
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class IdentityFunction extends FunctionNode
{
public $pathExpression;
/**
* @override
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
$dqlAlias = $this->pathExpression->identificationVariable;
$assocField = $this->pathExpression->field;
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
$class = $qComp['metadata'];
$assoc = $class->associationMappings[$assocField];
$tableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
return $tableAlias . '.' . reset($assoc['targetToSourceKeyColumns']);;
}
/**
* @override
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->pathExpression = $parser->SingleValuedAssociationPathExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

@ -53,8 +53,8 @@ class SizeFunction extends FunctionNode
if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) { if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) {
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->table['name']); $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
$sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE '; $sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE ';
@ -77,7 +77,7 @@ class SizeFunction extends FunctionNode
// SQL table aliases // SQL table aliases
$joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']); $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']);
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
// join to target table // join to target table
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE '; $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';

@ -23,7 +23,7 @@ namespace Doctrine\ORM\Query\AST;
/** /**
* SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable]
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
@ -37,11 +37,13 @@ class SelectExpression extends Node
{ {
public $expression; public $expression;
public $fieldIdentificationVariable; public $fieldIdentificationVariable;
public $hiddenAliasResultVariable;
public function __construct($expression, $fieldIdentificationVariable) public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false)
{ {
$this->expression = $expression; $this->expression = $expression;
$this->fieldIdentificationVariable = $fieldIdentificationVariable; $this->fieldIdentificationVariable = $fieldIdentificationVariable;
$this->hiddenAliasResultVariable = $hiddenAliasResultVariable;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)

@ -63,7 +63,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
$idColumnList = implode(', ', $idColumnNames); $idColumnList = implode(', ', $idColumnNames);
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $primaryDqlAlias); $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias);
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
. ' SELECT t0.' . implode(', t0.', $idColumnNames); . ' SELECT t0.' . implode(', t0.', $idColumnNames);
@ -98,7 +98,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
} }
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
} }
/** /**

@ -64,7 +64,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$idColumnList = implode(', ', $idColumnNames); $idColumnList = implode(', ', $idColumnNames);
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $updateClause->aliasIdentificationVariable); $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
. ' SELECT t0.' . implode(', t0.', $idColumnNames); . ' SELECT t0.' . implode(', t0.', $idColumnNames);
@ -106,7 +106,8 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
//FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage. //FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
if ($newValue instanceof AST\InputParameter) { if ($newValue instanceof AST\InputParameter) {
$paramKey = $newValue->name; $paramKey = $newValue->name;
$this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey); $this->_sqlParameters[$i]['parameters'][] = $sqlWalker->getQuery()->getParameter($paramKey);
$this->_sqlParameters[$i]['types'][] = $sqlWalker->getQuery()->getParameterType($paramKey);
++$this->_numParametersInUpdateClause; ++$this->_numParametersInUpdateClause;
} }
@ -136,7 +137,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
} }
/** /**
@ -154,11 +155,23 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
$conn->executeUpdate($this->_createTempTableSql); $conn->executeUpdate($this->_createTempTableSql);
// Insert identifiers. Parameters from the update clause are cut off. // Insert identifiers. Parameters from the update clause are cut off.
$numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types); $numUpdated = $conn->executeUpdate(
$this->_insertSql,
array_slice($params, $this->_numParametersInUpdateClause),
array_slice($types, $this->_numParametersInUpdateClause)
);
// Execute UPDATE statements // Execute UPDATE statements
for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) { for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
$conn->executeUpdate($this->_sqlStatements[$i], isset($this->_sqlParameters[$i]) ? $this->_sqlParameters[$i] : array()); $parameters = array();
$types = array();
if (isset($this->_sqlParameters[$i])) {
$parameters = isset($this->_sqlParameters[$i]['parameters']) ? $this->_sqlParameters[$i]['parameters'] : array();
$types = isset($this->_sqlParameters[$i]['types']) ? $this->_sqlParameters[$i]['types'] : array();
}
$conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
} }
// Drop temporary table // Drop temporary table

@ -51,6 +51,8 @@ abstract class Base
foreach ((array) $args as $arg) { foreach ((array) $args as $arg) {
$this->add($arg); $this->add($arg);
} }
return $this;
} }
public function add($arg) public function add($arg)
@ -67,6 +69,8 @@ abstract class Base
$this->_parts[] = $arg; $this->_parts[] = $arg;
} }
return $this;
} }
public function count() public function count()

@ -34,27 +34,55 @@ namespace Doctrine\ORM\Query\Expr;
*/ */
class From class From
{ {
/**
* @var string
*/
private $_from; private $_from;
/**
* @var string
*/
private $_alias; private $_alias;
public function __construct($from, $alias) /**
* @var string
*/
private $_indexBy;
/**
* @param string $from The class name.
* @param string $alias The alias of the class.
* @param string $indexBy The index for the from.
*/
public function __construct($from, $alias, $indexBy = null)
{ {
$this->_from = $from; $this->_from = $from;
$this->_alias = $alias; $this->_alias = $alias;
$this->_indexBy = $indexBy;
} }
/**
* @return string
*/
public function getFrom() public function getFrom()
{ {
return $this->_from; return $this->_from;
} }
/**
* @return string
*/
public function getAlias() public function getAlias()
{ {
return $this->_alias; return $this->_alias;
} }
/**
* @return string
*/
public function __toString() public function __toString()
{ {
return $this->_from . ' ' . $this->_alias; return $this->_from . ' ' . $this->_alias .
($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : '');
} }
} }

@ -76,39 +76,40 @@ class Lexer extends \Doctrine\Common\Lexer
const T_FROM = 122; const T_FROM = 122;
const T_GROUP = 123; const T_GROUP = 123;
const T_HAVING = 124; const T_HAVING = 124;
const T_IN = 125; const T_HIDDEN = 125;
const T_INDEX = 126; const T_IN = 126;
const T_INNER = 127; const T_INDEX = 127;
const T_INSTANCE = 128; const T_INNER = 128;
const T_IS = 129; const T_INSTANCE = 129;
const T_JOIN = 130; const T_IS = 130;
const T_LEADING = 131; const T_JOIN = 131;
const T_LEFT = 132; const T_LEADING = 132;
const T_LIKE = 133; const T_LEFT = 133;
const T_MAX = 134; const T_LIKE = 134;
const T_MEMBER = 135; const T_MAX = 135;
const T_MIN = 136; const T_MEMBER = 136;
const T_NOT = 137; const T_MIN = 137;
const T_NULL = 138; const T_NOT = 138;
const T_NULLIF = 139; const T_NULL = 139;
const T_OF = 140; const T_NULLIF = 140;
const T_OR = 141; const T_OF = 141;
const T_ORDER = 142; const T_OR = 142;
const T_OUTER = 143; const T_ORDER = 143;
const T_SELECT = 144; const T_OUTER = 144;
const T_SET = 145; const T_SELECT = 145;
const T_SIZE = 146; const T_SET = 146;
const T_SOME = 147; const T_SIZE = 147;
const T_SUM = 148; const T_SOME = 148;
const T_THEN = 149; const T_SUM = 149;
const T_TRAILING = 150; const T_THEN = 150;
const T_TRUE = 151; const T_TRAILING = 151;
const T_UPDATE = 152; const T_TRUE = 152;
const T_WHEN = 153; const T_UPDATE = 153;
const T_WHERE = 154; const T_WHEN = 154;
const T_WITH = 155; const T_WHERE = 155;
const T_PARTIAL = 156; const T_WITH = 156;
const T_MOD = 157; const T_PARTIAL = 157;
const T_MOD = 158;
/** /**
* Creates a new query scanner object. * Creates a new query scanner object.

@ -40,7 +40,8 @@ class Parser
'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction', 'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction', 'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction', 'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction' 'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction',
'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction',
); );
/** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */ /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
@ -1327,7 +1328,10 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->_lexer->glimpse(); $glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] != Lexer::T_DOT) { if ($glimpse['type'] == Lexer::T_DOT) {
return $this->SingleValuedPathExpression();
}
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable(); $identVariable = $this->IdentificationVariable();
@ -1338,9 +1342,6 @@ class Parser
return $identVariable; return $identVariable;
} }
return $this->SingleValuedPathExpression();
}
/** /**
* OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"] * OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"]
* *
@ -1353,12 +1354,9 @@ class Parser
// We need to check if we are in a ResultVariable or StateFieldPathExpression // We need to check if we are in a ResultVariable or StateFieldPathExpression
$glimpse = $this->_lexer->glimpse(); $glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] != Lexer::T_DOT) { $expr = ($glimpse['type'] != Lexer::T_DOT)
$token = $this->_lexer->lookahead; ? $this->ResultVariable()
$expr = $this->ResultVariable(); : $this->SingleValuedPathExpression();
} else {
$expr = $this->SingleValuedPathExpression();
}
$item = new AST\OrderByItem($expr); $item = new AST\OrderByItem($expr);
@ -1695,6 +1693,7 @@ class Parser
return $this->CoalesceExpression(); return $this->CoalesceExpression();
case Lexer::T_CASE: case Lexer::T_CASE:
$this->_lexer->resetPeek();
$peek = $this->_lexer->peek(); $peek = $this->_lexer->peek();
return ($peek['type'] === Lexer::T_WHEN) return ($peek['type'] === Lexer::T_WHEN)
@ -1829,7 +1828,7 @@ class Parser
/** /**
* SelectExpression ::= * SelectExpression ::=
* IdentificationVariable | StateFieldPathExpression | * IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
* *
* @return Doctrine\ORM\Query\AST\SelectExpression * @return Doctrine\ORM\Query\AST\SelectExpression
*/ */
@ -1837,6 +1836,7 @@ class Parser
{ {
$expression = null; $expression = null;
$identVariable = null; $identVariable = null;
$hiddenAliasResultVariable = false;
$fieldAliasIdentificationVariable = null; $fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
@ -1899,6 +1899,12 @@ class Parser
$this->match(Lexer::T_AS); $this->match(Lexer::T_AS);
} }
if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) {
$this->match(Lexer::T_HIDDEN);
$hiddenAliasResultVariable = true;
}
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$fieldAliasIdentificationVariable = $this->AliasResultVariable(); $fieldAliasIdentificationVariable = $this->AliasResultVariable();
@ -1912,10 +1918,12 @@ class Parser
} }
} }
$expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable); $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable, $hiddenAliasResultVariable);
if (!$supportsAlias) {
if ( ! $supportsAlias) {
$this->_identVariableExpressions[$identVariable] = $expr; $this->_identVariableExpressions[$identVariable] = $expr;
} }
return $expr; return $expr;
} }
@ -2381,7 +2389,7 @@ class Parser
/** /**
* ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
* | FunctionsReturningDatetime | IdentificationVariable | CaseExpression * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable | CaseExpression
*/ */
public function ArithmeticPrimary() public function ArithmeticPrimary()
{ {
@ -2411,6 +2419,10 @@ class Parser
return $this->SingleValuedPathExpression(); return $this->SingleValuedPathExpression();
} }
if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) {
return $this->ResultVariable();
}
return $this->StateFieldPathExpression(); return $this->StateFieldPathExpression();
case Lexer::T_INPUT_PARAMETER: case Lexer::T_INPUT_PARAMETER:

@ -244,24 +244,23 @@ class SqlWalker implements TreeWalker
{ {
$sql = ''; $sql = '';
$baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
// INNER JOIN parent class tables // INNER JOIN parent class tables
foreach ($class->parentClasses as $parentClassName) { foreach ($class->parentClasses as $parentClassName) {
$parentClass = $this->_em->getClassMetadata($parentClassName); $parentClass = $this->_em->getClassMetadata($parentClassName);
$tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
// If this is a joined association we must use left joins to preserve the correct result. // If this is a joined association we must use left joins to preserve the correct result.
$sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
. ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($class->identifier as $idField) { foreach ($class->identifier as $idField) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$columnName = $class->getQuotedColumnName($idField, $this->_platform); $columnName = $class->getQuotedColumnName($idField, $this->_platform);
$sql .= $baseTableAlias . '.' . $columnName $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
. ' = '
. $tableAlias . '.' . $columnName;
} }
} }
@ -269,17 +268,15 @@ class SqlWalker implements TreeWalker
if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { if ( ! $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);
$tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
. ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($class->identifier as $idField) { foreach ($class->identifier as $idField) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$columnName = $class->getQuotedColumnName($idField, $this->_platform); $columnName = $class->getQuotedColumnName($idField, $this->_platform);
$sql .= $baseTableAlias . '.' . $columnName $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
. ' = '
. $tableAlias . '.' . $columnName;
} }
} }
} }
@ -290,24 +287,26 @@ class SqlWalker implements TreeWalker
private function _generateOrderedCollectionOrderByItems() private function _generateOrderedCollectionOrderByItems()
{ {
$sql = ''; $sql = '';
foreach ($this->_selectedClasses AS $dqlAlias => $class) { foreach ($this->_selectedClasses AS $dqlAlias => $class) {
$qComp = $this->_queryComponents[$dqlAlias]; $qComp = $this->_queryComponents[$dqlAlias];
if (isset($qComp['relation']['orderBy'])) { if (isset($qComp['relation']['orderBy'])) {
foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
if ($qComp['metadata']->isInheritanceTypeJoined()) { $tableName = ($qComp['metadata']->isInheritanceTypeJoined())
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
} else { : $qComp['metadata']->getTableName();
$tableName = $qComp['metadata']->table['name'];
}
if ($sql != '') { if ($sql != '') {
$sql .= ', '; $sql .= ', ';
} }
$sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' .
$qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation"; $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.'
. $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . ' ' . $orientation;
} }
} }
} }
return $sql; return $sql;
} }
@ -328,6 +327,7 @@ class SqlWalker implements TreeWalker
if ($class->isInheritanceTypeSingleTable()) { if ($class->isInheritanceTypeSingleTable()) {
$conn = $this->_em->getConnection(); $conn = $this->_em->getConnection();
$values = array(); $values = array();
if ($class->discriminatorValue !== null) { // discrimnators can be 0 if ($class->discriminatorValue !== null) { // discrimnators can be 0
$values[] = $conn->quote($class->discriminatorValue); $values[] = $conn->quote($class->discriminatorValue);
} }
@ -342,7 +342,7 @@ class SqlWalker implements TreeWalker
} }
$sql .= ($sql != '' ? ' AND ' : '') $sql .= ($sql != '' ? ' AND ' : '')
. (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.' : '') . (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '')
. $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
} }
} }
@ -366,19 +366,18 @@ class SqlWalker implements TreeWalker
if (($orderByClause = $AST->orderByClause) !== null) { if (($orderByClause = $AST->orderByClause) !== null) {
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
} else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { } else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') {
$sql .= ' ORDER BY '.$orderBySql; $sql .= ' ORDER BY ' . $orderBySql;
} }
$sql = $this->_platform->modifyLimitQuery( $sql = $this->_platform->modifyLimitQuery(
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult() $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
); );
if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) { if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
if ($lockMode == LockMode::PESSIMISTIC_READ) { if ($lockMode == LockMode::PESSIMISTIC_READ) {
$sql .= " " . $this->_platform->getReadLockSQL(); $sql .= ' ' . $this->_platform->getReadLockSQL();
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
$sql .= " " . $this->_platform->getWriteLockSQL(); $sql .= ' ' . $this->_platform->getWriteLockSQL();
} else if ($lockMode == LockMode::OPTIMISTIC) { } else if ($lockMode == LockMode::OPTIMISTIC) {
foreach ($this->_selectedClasses AS $class) { foreach ($this->_selectedClasses AS $class) {
if ( ! $class->isVersioned) { if ( ! $class->isVersioned) {
@ -400,10 +399,8 @@ class SqlWalker implements TreeWalker
public function walkUpdateStatement(AST\UpdateStatement $AST) public function walkUpdateStatement(AST\UpdateStatement $AST)
{ {
$this->_useSqlTableAliases = false; $this->_useSqlTableAliases = false;
$sql = $this->walkUpdateClause($AST->updateClause);
$sql .= $this->walkWhereClause($AST->whereClause);
return $sql; return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause);
} }
/** /**
@ -415,10 +412,8 @@ class SqlWalker implements TreeWalker
public function walkDeleteStatement(AST\DeleteStatement $AST) public function walkDeleteStatement(AST\DeleteStatement $AST)
{ {
$this->_useSqlTableAliases = false; $this->_useSqlTableAliases = false;
$sql = $this->walkDeleteClause($AST->deleteClause);
$sql .= $this->walkWhereClause($AST->whereClause);
return $sql; return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause);
} }
@ -440,7 +435,7 @@ class SqlWalker implements TreeWalker
$class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); $class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
} }
return $this->getSQLTableAlias($class->table['name'], $identificationVariable); return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
} }
/** /**
@ -489,7 +484,7 @@ class SqlWalker implements TreeWalker
} }
if ($this->_useSqlTableAliases) { if ($this->_useSqlTableAliases) {
$sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.'; $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
} }
$sql .= reset($assoc['targetToSourceKeyColumns']); $sql .= reset($assoc['targetToSourceKeyColumns']);
@ -534,7 +529,7 @@ class SqlWalker implements TreeWalker
if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
// Add discriminator columns to SQL // Add discriminator columns to SQL
$rootClass = $this->_em->getClassMetadata($class->rootEntityName); $rootClass = $this->_em->getClassMetadata($class->rootEntityName);
$tblAlias = $this->getSQLTableAlias($rootClass->table['name'], $dqlAlias); $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
$discrColumn = $rootClass->discriminatorColumn; $discrColumn = $rootClass->discriminatorColumn;
$columnAlias = $this->getSQLColumnAlias($discrColumn['name']); $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
@ -551,9 +546,9 @@ class SqlWalker implements TreeWalker
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
if (isset($assoc['inherited'])) { if (isset($assoc['inherited'])) {
$owningClass = $this->_em->getClassMetadata($assoc['inherited']); $owningClass = $this->_em->getClassMetadata($assoc['inherited']);
$sqlTableAlias = $this->getSQLTableAlias($owningClass->table['name'], $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
} else { } else {
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
} }
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
@ -570,7 +565,8 @@ class SqlWalker implements TreeWalker
} else { } else {
// Add foreign key columns to SQL, if necessary // Add foreign key columns to SQL, if necessary
if ($addMetaColumns) { if ($addMetaColumns) {
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
foreach ($class->associationMappings as $assoc) { foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
@ -612,7 +608,7 @@ class SqlWalker implements TreeWalker
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' ' $sql .= $class->getQuotedTableName($this->_platform) . ' '
. $this->getSQLTableAlias($class->table['name'], $dqlAlias); . $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
if ($class->isInheritanceTypeJoined()) { if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
@ -724,12 +720,14 @@ class SqlWalker implements TreeWalker
$joinAssocPathExpr = $join->joinAssociationPathExpression; $joinAssocPathExpr = $join->joinAssociationPathExpression;
$joinedDqlAlias = $join->aliasIdentificationVariable; $joinedDqlAlias = $join->aliasIdentificationVariable;
$relation = $this->_queryComponents[$joinedDqlAlias]['relation']; $relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']); $targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']); $sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
$targetTableName = $targetClass->getQuotedTableName($this->_platform); $targetTableName = $targetClass->getQuotedTableName($this->_platform);
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name'], $joinedDqlAlias);
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinAssocPathExpr->identificationVariable);
// Ensure we got the owning side, since it has all mapping info // Ensure we got the owning side, since it has all mapping info
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
@ -811,8 +809,7 @@ class SqlWalker implements TreeWalker
} }
// Join target table // Join target table
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
? ' LEFT JOIN ' : ' INNER JOIN ';
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
$first = true; $first = true;
@ -980,6 +977,7 @@ class SqlWalker implements TreeWalker
{ {
$sql = ''; $sql = '';
$expr = $selectExpression->expression; $expr = $selectExpression->expression;
$hidden = $selectExpression->hiddenAliasResultVariable;
if ($expr instanceof AST\PathExpression) { if ($expr instanceof AST\PathExpression) {
if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
@ -1009,7 +1007,10 @@ class SqlWalker implements TreeWalker
$columnAlias = $this->getSQLColumnAlias($columnName); $columnAlias = $this->getSQLColumnAlias($columnName);
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else if ($expr instanceof AST\AggregateExpression) { } else if ($expr instanceof AST\AggregateExpression) {
if ( ! $selectExpression->fieldIdentificationVariable) { if ( ! $selectExpression->fieldIdentificationVariable) {
$resultAlias = $this->_scalarResultCounter++; $resultAlias = $this->_scalarResultCounter++;
@ -1022,7 +1023,10 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else if ($expr instanceof AST\Subselect) { } else if ($expr instanceof AST\Subselect) {
if ( ! $selectExpression->fieldIdentificationVariable) { if ( ! $selectExpression->fieldIdentificationVariable) {
$resultAlias = $this->_scalarResultCounter++; $resultAlias = $this->_scalarResultCounter++;
@ -1035,7 +1039,10 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else if ($expr instanceof AST\Functions\FunctionNode) { } else if ($expr instanceof AST\Functions\FunctionNode) {
if ( ! $selectExpression->fieldIdentificationVariable) { if ( ! $selectExpression->fieldIdentificationVariable) {
$resultAlias = $this->_scalarResultCounter++; $resultAlias = $this->_scalarResultCounter++;
@ -1048,7 +1055,10 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else if ( } else if (
$expr instanceof AST\SimpleArithmeticExpression || $expr instanceof AST\SimpleArithmeticExpression ||
$expr instanceof AST\ArithmeticTerm || $expr instanceof AST\ArithmeticTerm ||
@ -1073,7 +1083,10 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else if ( } else if (
$expr instanceof AST\NullIfExpression || $expr instanceof AST\NullIfExpression ||
$expr instanceof AST\CoalesceExpression || $expr instanceof AST\CoalesceExpression ||
@ -1093,7 +1106,10 @@ class SqlWalker implements TreeWalker
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
}
} else { } else {
// IdentificationVariable or PartialObjectExpression // IdentificationVariable or PartialObjectExpression
if ($expr instanceof AST\PartialObjectExpression) { if ($expr instanceof AST\PartialObjectExpression) {
@ -1112,17 +1128,16 @@ class SqlWalker implements TreeWalker
} }
$beginning = true; $beginning = true;
// 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;
} }
if (isset($mapping['inherited'])) { $tableName = (isset($mapping['inherited']))
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName()
} else { : $class->getTableName();
$tableName = $class->table['name'];
}
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
@ -1132,6 +1147,7 @@ class SqlWalker implements TreeWalker
. ' AS ' . $columnAlias; . ' AS ' . $columnAlias;
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
} }
@ -1142,7 +1158,8 @@ class SqlWalker implements TreeWalker
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->table['name'], $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
continue; continue;
@ -1164,8 +1181,10 @@ class SqlWalker implements TreeWalker
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
$columnAlias = $this->getSQLColumnAlias($srcColumn); $columnAlias = $this->getSQLColumnAlias($srcColumn);
$sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
} }
} }
@ -1235,7 +1254,7 @@ class SqlWalker implements TreeWalker
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' ' $sql .= $class->getQuotedTableName($this->_platform) . ' '
. $this->getSQLTableAlias($class->table['name'], $dqlAlias); . $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
$this->_rootAliases[] = $dqlAlias; $this->_rootAliases[] = $dqlAlias;
@ -1320,6 +1339,22 @@ class SqlWalker implements TreeWalker
$columnAlias = 'sclr' . $this->_aliasCounter++; $columnAlias = 'sclr' . $this->_aliasCounter++;
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias;
} else if (
$expr instanceof AST\NullIfExpression ||
$expr instanceof AST\CoalesceExpression ||
$expr instanceof AST\GeneralCaseExpression ||
$expr instanceof AST\SimpleCaseExpression
) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = 'sclr' . $this->_aliasCounter++;
$sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias;
} else { } else {
// IdentificationVariable // IdentificationVariable
@ -1590,14 +1625,12 @@ class SqlWalker implements TreeWalker
if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
$sql .= $targetClass->getQuotedTableName($this->_platform) $sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE ';
. ' ' . $targetTableAlias . ' WHERE ';
$owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
$first = true; $first = true;
foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
@ -1626,14 +1659,12 @@ class SqlWalker implements TreeWalker
// SQL table aliases // SQL table aliases
$joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
// join to target table // join to target table
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) . ' ' . $joinTableAlias
. ' ' . $joinTableAlias . ' INNER JOIN ' . ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON ';
. $targetClass->getQuotedTableName($this->_platform)
. ' ' . $targetTableAlias . ' ON ';
// join conditions // join conditions
$joinColumns = $assoc['isOwningSide'] $joinColumns = $assoc['isOwningSide']
@ -1645,25 +1676,19 @@ class SqlWalker implements TreeWalker
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
. $targetTableAlias . '.' . $targetClass->getQuotedColumnName( . $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
$targetClass->fieldNames[$joinColumn['referencedColumnName']],
$this->_platform);
} }
$sql .= ' WHERE '; $sql .= ' WHERE ';
$joinColumns = $assoc['isOwningSide'] $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
? $joinTable['joinColumns']
: $joinTable['inverseJoinColumns'];
$first = true; $first = true;
foreach ($joinColumns as $joinColumn) { foreach ($joinColumns as $joinColumn) {
if ($first) $first = false; else $sql .= ' AND '; if ($first) $first = false; else $sql .= ' AND ';
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
. $sourceTableAlias . '.' . $class->getQuotedColumnName( . $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $this->_platform);
$class->fieldNames[$joinColumn['referencedColumnName']],
$this->_platform);
} }
$sql .= ' AND '; $sql .= ' AND ';
@ -1760,7 +1785,7 @@ class SqlWalker implements TreeWalker
} }
if ($this->_useSqlTableAliases) { if ($this->_useSqlTableAliases) {
$sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.'; $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
} }
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
@ -1825,12 +1850,16 @@ class SqlWalker implements TreeWalker
switch ($literal->type) { switch ($literal->type) {
case AST\Literal::STRING: case AST\Literal::STRING:
return $this->_conn->quote($literal->value); return $this->_conn->quote($literal->value);
case AST\Literal::BOOLEAN: case AST\Literal::BOOLEAN:
$bool = strtolower($literal->value) == 'true' ? true : false; $bool = strtolower($literal->value) == 'true' ? true : false;
$boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
return $boolVal;
case AST\Literal::NUMERIC: case AST\Literal::NUMERIC:
return $literal->value; return $literal->value;
default: default:
throw QueryException::invalidLiteral($literal); throw QueryException::invalidLiteral($literal);
} }
@ -1971,6 +2000,12 @@ class SqlWalker implements TreeWalker
public function walkArithmeticTerm($term) public function walkArithmeticTerm($term)
{ {
if (is_string($term)) { if (is_string($term)) {
if (isset($this->_queryComponents[$term])) {
$columnName = $this->_queryComponents[$term]['token']['value'];
return $this->_scalarResultAliasMap[$columnName];
}
return $term; return $term;
} }

@ -595,11 +595,12 @@ class QueryBuilder
* *
* @param string $from The class name. * @param string $from The class name.
* @param string $alias The alias of the class. * @param string $alias The alias of the class.
* @param string $indexBy The index for the from.
* @return QueryBuilder This QueryBuilder instance. * @return QueryBuilder This QueryBuilder instance.
*/ */
public function from($from, $alias) public function from($from, $alias, $indexBy = null)
{ {
return $this->add('from', new Expr\From($from, $alias), true); return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
} }
/** /**

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
use Symfony\Component\Console\Input\InputArgument, use Symfony\Component\Console\Input\InputArgument,
Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputOption,
Symfony\Component\Console; Symfony\Component\Console,
Doctrine\Common\Cache;
/** /**
* Command to clear the metadata cache of the various cache drivers. * Command to clear the metadata cache of the various cache drivers.
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
@ -47,9 +45,30 @@ class MetadataCommand extends Console\Command\Command
$this $this
->setName('orm:clear-cache:metadata') ->setName('orm:clear-cache:metadata')
->setDescription('Clear all metadata cache of the various cache drivers.') ->setDescription('Clear all metadata cache of the various cache drivers.')
->setDefinition(array()) ->setDefinition(array(
->setHelp(<<<EOT new InputOption(
Clear all metadata cache of the various cache drivers. 'flush', null, InputOption::VALUE_NONE,
'If defined, cache entries will be flushed instead of deleted/invalidated.'
)
));
$fullName = $this->getName();
$this->setHelp(<<<EOT
The <info>$fullName</info> command is meant to clear the metadata cache of associated Entity Manager.
It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
instance completely.
The execution type differ on how you execute the command.
If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
<info>$fullName</info>
Alternatively, if you want to flush the cache provider using this command:
<info>$fullName --flush</info>
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
because of a limitation of its execution nature.
EOT EOT
); );
} }
@ -66,20 +85,20 @@ EOT
throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.');
} }
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) { if ($cacheDriver instanceof Cache\ApcCache) {
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
} }
$output->write('Clearing ALL Metadata cache entries' . PHP_EOL); $output->write('Clearing ALL Metadata cache entries' . PHP_EOL);
$cacheIds = $cacheDriver->deleteAll(); $result = $cacheDriver->deleteAll();
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
if ($cacheIds) { if (true === $input->getOption('flush')) {
foreach ($cacheIds as $cacheId) { $result = $cacheDriver->flushAll();
$output->write(' - ' . $cacheId . PHP_EOL); $message = ($result) ? 'Successfully flushed cache entries.' : $message;
}
} else {
$output->write('No entries to be deleted.' . PHP_EOL);
} }
$output->write($message . PHP_EOL);
} }
} }

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
use Symfony\Component\Console\Input\InputArgument, use Symfony\Component\Console\Input\InputArgument,
Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputOption,
Symfony\Component\Console; Symfony\Component\Console,
Doctrine\Common\Cache;
/** /**
* Command to clear the query cache of the various cache drivers. * Command to clear the query cache of the various cache drivers.
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
@ -47,9 +45,30 @@ class QueryCommand extends Console\Command\Command
$this $this
->setName('orm:clear-cache:query') ->setName('orm:clear-cache:query')
->setDescription('Clear all query cache of the various cache drivers.') ->setDescription('Clear all query cache of the various cache drivers.')
->setDefinition(array()) ->setDefinition(array(
->setHelp(<<<EOT new InputOption(
Clear all query cache of the various cache drivers. 'flush', null, InputOption::VALUE_NONE,
'If defined, cache entries will be flushed instead of deleted/invalidated.'
)
));
$fullName = $this->getName();
$this->setHelp(<<<EOT
The <info>$fullName</info> command is meant to clear the query cache of associated Entity Manager.
It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
instance completely.
The execution type differ on how you execute the command.
If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
<info>$fullName</info>
Alternatively, if you want to flush the cache provider using this command:
<info>$fullName --flush</info>
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
because of a limitation of its execution nature.
EOT EOT
); );
} }
@ -66,20 +85,20 @@ EOT
throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.');
} }
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) { if ($cacheDriver instanceof Cache\ApcCache) {
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
} }
$output->write('Clearing ALL Query cache entries' . PHP_EOL); $output->write('Clearing ALL Query cache entries' . PHP_EOL);
$cacheIds = $cacheDriver->deleteAll(); $result = $cacheDriver->deleteAll();
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
if ($cacheIds) { if (true === $input->getOption('flush')) {
foreach ($cacheIds as $cacheId) { $result = $cacheDriver->flushAll();
$output->write(' - ' . $cacheId . PHP_EOL); $message = ($result) ? 'Successfully flushed cache entries.' : $message;
}
} else {
$output->write('No entries to be deleted.' . PHP_EOL);
} }
$output->write($message . PHP_EOL);
} }
} }

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -23,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
use Symfony\Component\Console\Input\InputArgument, use Symfony\Component\Console\Input\InputArgument,
Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputOption,
Symfony\Component\Console; Symfony\Component\Console,
Doctrine\Common\Cache;
/** /**
* Command to clear the result cache of the various cache drivers. * Command to clear the result cache of the various cache drivers.
@ -31,7 +30,6 @@ use Symfony\Component\Console\Input\InputArgument,
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
@ -46,28 +44,31 @@ class ResultCommand extends Console\Command\Command
{ {
$this $this
->setName('orm:clear-cache:result') ->setName('orm:clear-cache:result')
->setDescription('Clear result cache of the various cache drivers.') ->setDescription('Clear all result cache of the various cache drivers.')
->setDefinition(array( ->setDefinition(array(
new InputOption( new InputOption(
'id', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'flush', null, InputOption::VALUE_NONE,
'ID(s) of the cache entry to delete (accepts * wildcards).', array() 'If defined, cache entries will be flushed instead of deleted/invalidated.'
), )
new InputOption( ));
'regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Delete cache entries that match the given regular expression(s).', array() $fullName = $this->getName();
), $this->setHelp(<<<EOT
new InputOption( The <info>$fullName</info> command is meant to clear the result cache of associated Entity Manager.
'prefix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider
'Delete cache entries that have the given prefix(es).', array() instance completely.
),
new InputOption( The execution type differ on how you execute the command.
'suffix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, If you want to invalidate the entries (and not delete from cache instance), this command would do the work:
'Delete cache entries that have the given suffix(es).', array()
), <info>$fullName</info>
))
->setHelp(<<<EOT Alternatively, if you want to flush the cache provider using this command:
Clear result cache of the various cache drivers.
If none of the options are defined, all cache entries will be removed. <info>$fullName --flush</info>
Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries,
because of a limitation of its execution nature.
EOT EOT
); );
} }
@ -84,85 +85,20 @@ EOT
throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.');
} }
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) { if ($cacheDriver instanceof Cache\ApcCache) {
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
} }
$outputed = false;
// Removing based on --id
if (($ids = $input->getOption('id')) !== null && $ids) {
foreach ($ids as $id) {
$output->write($outputed ? PHP_EOL : '');
$output->write(sprintf('Clearing Result cache entries that match the id "<info>%s</info>"', $id) . PHP_EOL);
$deleted = $cacheDriver->delete($id);
if (is_array($deleted)) {
$this->_printDeleted($output, $deleted);
} else if (is_bool($deleted) && $deleted) {
$this->_printDeleted($output, array($id));
}
$outputed = true;
}
}
// Removing based on --regex
if (($regexps = $input->getOption('regex')) !== null && $regexps) {
foreach($regexps as $regex) {
$output->write($outputed ? PHP_EOL : '');
$output->write(sprintf('Clearing Result cache entries that match the regular expression "<info>%s</info>"', $regex) . PHP_EOL);
$this->_printDeleted($output, $cacheDriver->deleteByRegex('/' . $regex. '/'));
$outputed = true;
}
}
// Removing based on --prefix
if (($prefixes = $input->getOption('prefix')) !== null & $prefixes) {
foreach ($prefixes as $prefix) {
$output->write($outputed ? PHP_EOL : '');
$output->write(sprintf('Clearing Result cache entries that have the prefix "<info>%s</info>"', $prefix) . PHP_EOL);
$this->_printDeleted($output, $cacheDriver->deleteByPrefix($prefix));
$outputed = true;
}
}
// Removing based on --suffix
if (($suffixes = $input->getOption('suffix')) !== null && $suffixes) {
foreach ($suffixes as $suffix) {
$output->write($outputed ? PHP_EOL : '');
$output->write(sprintf('Clearing Result cache entries that have the suffix "<info>%s</info>"', $suffix) . PHP_EOL);
$this->_printDeleted($output, $cacheDriver->deleteBySuffix($suffix));
$outputed = true;
}
}
// Removing ALL entries
if ( ! $ids && ! $regexps && ! $prefixes && ! $suffixes) {
$output->write($outputed ? PHP_EOL : '');
$output->write('Clearing ALL Result cache entries' . PHP_EOL); $output->write('Clearing ALL Result cache entries' . PHP_EOL);
$this->_printDeleted($output, $cacheDriver->deleteAll()); $result = $cacheDriver->deleteAll();
$message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.';
$outputed = true; if (true === $input->getOption('flush')) {
} $result = $cacheDriver->flushAll();
$message = ($result) ? 'Successfully flushed cache entries.' : $message;
} }
private function _printDeleted(Console\Output\OutputInterface $output, array $items) $output->write($message . PHP_EOL);
{
if ($items) {
foreach ($items as $item) {
$output->write(' - ' . $item . PHP_EOL);
}
} else {
$output->write('No entries to be deleted.' . PHP_EOL);
}
} }
} }

@ -137,7 +137,7 @@ EOT
if ( ! file_exists($destPath)) { if ( ! file_exists($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
sprintf("Mapping destination directory '<info>%s</info>' does not exist.", $destPath) sprintf("Mapping destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
); );
} else if ( ! is_writable($destPath)) { } else if ( ! is_writable($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(

@ -123,7 +123,7 @@ EOT
if ( ! file_exists($destPath)) { if ( ! file_exists($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $destPath) sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
); );
} else if ( ! is_writable($destPath)) { } else if ( ! is_writable($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(

@ -87,7 +87,7 @@ EOT
if ( ! file_exists($destPath)) { if ( ! file_exists($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $destPath) sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $em->getConfiguration()->getProxyDir())
); );
} else if ( ! is_writable($destPath)) { } else if ( ! is_writable($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(

@ -79,7 +79,7 @@ EOT
if ( ! file_exists($destPath)) { if ( ! file_exists($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
sprintf("Entities destination directory '<info>%s</info>' does not exist.", $destPath) sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path'))
); );
} else if ( ! is_writable($destPath)) { } else if ( ! is_writable($destPath)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(

@ -46,7 +46,7 @@ class ValidateSchemaCommand extends Console\Command\Command
{ {
$this $this
->setName('orm:validate-schema') ->setName('orm:validate-schema')
->setDescription('Validate that the mapping files.') ->setDescription('Validate the mapping files.')
->setHelp(<<<EOT ->setHelp(<<<EOT
'Validate that the mapping files are correct and in sync with the database.' 'Validate that the mapping files are correct and in sync with the database.'
EOT EOT

@ -101,6 +101,7 @@ class ConvertDoctrine1Schema
{ {
if (isset($model['tableName']) && $model['tableName']) { if (isset($model['tableName']) && $model['tableName']) {
$e = explode('.', $model['tableName']); $e = explode('.', $model['tableName']);
if (count($e) > 1) { if (count($e) > 1) {
$metadata->table['schema'] = $e[0]; $metadata->table['schema'] = $e[0];
$metadata->table['name'] = $e[1]; $metadata->table['name'] = $e[1];

@ -115,10 +115,12 @@ public function <methodName>()
* <description> * <description>
* *
* @param <variableType>$<variableName> * @param <variableType>$<variableName>
* @return <entity>
*/ */
public function <methodName>(<methodTypeHint>$<variableName>) public function <methodName>(<methodTypeHint>$<variableName>)
{ {
<spaces>$this-><fieldName> = $<variableName>; <spaces>$this-><fieldName> = $<variableName>;
<spaces>return $this;
}'; }';
private static $_addMethodTemplate = private static $_addMethodTemplate =
@ -150,7 +152,7 @@ public function <methodName>()
public function __construct() public function __construct()
{ {
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) { if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
$this->_annotationsPrefix = 'ORM\\'; $this->_annotationsPrefix = 'ORM\\';
} }
} }
@ -302,9 +304,6 @@ public function <methodName>()
*/ */
public function setAnnotationPrefix($prefix) public function setAnnotationPrefix($prefix)
{ {
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
return;
}
$this->_annotationsPrefix = $prefix; $this->_annotationsPrefix = $prefix;
} }
@ -399,14 +398,17 @@ public function <methodName>()
} }
$collections = array(); $collections = array();
foreach ($metadata->associationMappings AS $mapping) { foreach ($metadata->associationMappings AS $mapping) {
if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
$collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
} }
} }
if ($collections) { if ($collections) {
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate)); return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate));
} }
return ''; return '';
} }
@ -570,7 +572,12 @@ public function <methodName>()
private function _generateTableAnnotation($metadata) private function _generateTableAnnotation($metadata)
{ {
$table = array(); $table = array();
if ($metadata->table['name']) {
if (isset($metadata->table['schema'])) {
$table[] = 'schema="' . $metadata->table['schema'] . '"';
}
if (isset($metadata->table['name'])) {
$table[] = 'name="' . $metadata->table['name'] . '"'; $table[] = 'name="' . $metadata->table['name'] . '"';
} }
@ -729,7 +736,8 @@ public function <methodName>()
'<variableType>' => $variableType, '<variableType>' => $variableType,
'<variableName>' => Inflector::camelize($fieldName), '<variableName>' => Inflector::camelize($fieldName),
'<methodName>' => $methodName, '<methodName>' => $methodName,
'<fieldName>' => $fieldName '<fieldName>' => $fieldName,
'<entity>' => $this->_getClassName($metadata)
); );
$method = str_replace( $method = str_replace(
@ -783,7 +791,7 @@ public function <methodName>()
} }
if (isset($joinColumn['onDelete'])) { if (isset($joinColumn['onDelete'])) {
$joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false'); $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
} }
if (isset($joinColumn['columnDefinition'])) { if (isset($joinColumn['columnDefinition'])) {

@ -49,11 +49,13 @@ class YamlExporter extends AbstractExporter
public function exportClassMetadata(ClassMetadataInfo $metadata) public function exportClassMetadata(ClassMetadataInfo $metadata)
{ {
$array = array(); $array = array();
if ($metadata->isMappedSuperclass) { if ($metadata->isMappedSuperclass) {
$array['type'] = 'mappedSuperclass'; $array['type'] = 'mappedSuperclass';
} else { } else {
$array['type'] = 'entity'; $array['type'] = 'entity';
} }
$array['table'] = $metadata->table['name']; $array['table'] = $metadata->table['name'];
if (isset($metadata->table['schema'])) { if (isset($metadata->table['schema'])) {
@ -81,6 +83,10 @@ class YamlExporter extends AbstractExporter
$array['indexes'] = $metadata->table['indexes']; $array['indexes'] = $metadata->table['indexes'];
} }
if ($metadata->customRepositoryClassName) {
$array['repositoryClass'] = $metadata->customRepositoryClassName;
}
if (isset($metadata->table['uniqueConstraints'])) { if (isset($metadata->table['uniqueConstraints'])) {
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints']; $array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
} }

@ -152,6 +152,17 @@ class SchemaValidator
"has to be a primary key column."; "has to be a primary key column.";
} }
} }
if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) {
$ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
"have to match to ALL identifier columns of the target entity '". $targetClass->name . "'";
}
if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) {
$ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " .
"have to match to ALL identifier columns of the source entity '". $class->name . "'";
}
} else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
foreach ($assoc['joinColumns'] AS $joinColumn) { foreach ($assoc['joinColumns'] AS $joinColumn) {
$targetClass = $cmf->getMetadataFor($assoc['targetEntity']); $targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
@ -167,6 +178,11 @@ class SchemaValidator
"has to be a primary key column."; "has to be a primary key column.";
} }
} }
if (count($class->identifier) != count($assoc['joinColumns'])) {
$ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " .
"have to match to ALL identifier columns of the source entity '". $class->name . "'";
}
} }
} }

@ -97,7 +97,7 @@ class Setup
require_once $directory . "/Doctrine/Common/ClassLoader.php"; require_once $directory . "/Doctrine/Common/ClassLoader.php";
} }
$loader = new ClassLoader("Doctrine"); $loader = new ClassLoader("Doctrine", $directory);
$loader->register(); $loader->register();
$loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine"); $loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine");
@ -115,13 +115,13 @@ class Setup
*/ */
static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{ {
$config = self::createConfiguration($isDevMode, $cache, $proxyDir); $config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths)); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths));
return $config; return $config;
} }
/** /**
* Create a configuration with an annotation metadata driver. * Create a configuration with a xml metadata driver.
* *
* @param array $paths * @param array $paths
* @param boolean $isDevMode * @param boolean $isDevMode
@ -131,13 +131,13 @@ class Setup
*/ */
static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{ {
$config = self::createConfiguration($isDevMode, $cache, $proxyDir); $config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new XmlDriver($paths)); $config->setMetadataDriverImpl(new XmlDriver($paths));
return $config; return $config;
} }
/** /**
* Create a configuration with an annotation metadata driver. * Create a configuration with a yaml metadata driver.
* *
* @param array $paths * @param array $paths
* @param boolean $isDevMode * @param boolean $isDevMode
@ -147,7 +147,7 @@ class Setup
*/ */
static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
{ {
$config = self::createConfiguration($isDevMode, $cache, $proxyDir); $config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new YamlDriver($paths)); $config->setMetadataDriverImpl(new YamlDriver($paths));
return $config; return $config;
} }
@ -162,6 +162,7 @@ class Setup
*/ */
static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null) static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null)
{ {
$proxyDir = $proxyDir ?: sys_get_temp_dir();
if ($isDevMode === false && $cache === null) { if ($isDevMode === false && $cache === null) {
if (extension_loaded('apc')) { if (extension_loaded('apc')) {
$cache = new \Doctrine\Common\Cache\ApcCache; $cache = new \Doctrine\Common\Cache\ApcCache;
@ -175,16 +176,16 @@ class Setup
} else { } else {
$cache = new ArrayCache; $cache = new ArrayCache;
} }
$cache->setNamespace("dc2_"); // to avoid collisions
} else if ($cache === null) { } else if ($cache === null) {
$cache = new ArrayCache; $cache = new ArrayCache;
} }
$cache->setNamespace("dc2_" . md5($proxyDir) . "_"); // to avoid collisions
$config = new Configuration(); $config = new Configuration();
$config->setMetadataCacheImpl($cache); $config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache); $config->setQueryCacheImpl($cache);
$config->setResultCacheImpl($cache); $config->setResultCacheImpl($cache);
$config->setProxyDir( $proxyDir ?: sys_get_temp_dir() ); $config->setProxyDir( $proxyDir );
$config->setProxyNamespace('DoctrineProxies'); $config->setProxyNamespace('DoctrineProxies');
$config->setAutoGenerateProxyClasses($isDevMode); $config->setAutoGenerateProxyClasses($isDevMode);

@ -216,7 +216,12 @@ class UnitOfWork implements PropertyChangedListener
*/ */
private $orphanRemovals = array(); private $orphanRemovals = array();
//private $_readOnlyObjects = array(); /**
* Read-Only objects are never evaluated
*
* @var array
*/
private $readOnlyObjects = array();
/** /**
* Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
@ -393,6 +398,8 @@ class UnitOfWork implements PropertyChangedListener
* If a PersistentCollection has been de-referenced in a fully MANAGED entity, * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
* then this collection is marked for deletion. * then this collection is marked for deletion.
* *
* @ignore
* @internal Don't call from the outside.
* @param ClassMetadata $class The class descriptor of the entity. * @param ClassMetadata $class The class descriptor of the entity.
* @param object $entity The entity for which to compute the changes. * @param object $entity The entity for which to compute the changes.
*/ */
@ -403,6 +410,11 @@ class UnitOfWork implements PropertyChangedListener
} }
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if (isset($this->readOnlyObjects[$oid])) {
return;
}
$actualData = array(); $actualData = array();
foreach ($class->reflFields as $name => $refProp) { foreach ($class->reflFields as $name => $refProp) {
$value = $refProp->getValue($entity); $value = $refProp->getValue($entity);
@ -458,7 +470,15 @@ class UnitOfWork implements PropertyChangedListener
$changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array();
foreach ($actualData as $propName => $actualValue) { foreach ($actualData as $propName => $actualValue) {
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (isset($originalData[$propName])) {
$orgValue = $originalData[$propName];
} else if (array_key_exists($propName, $originalData)) {
$orgValue = null;
} else {
// skip field, its a partially omitted one!
continue;
}
if (isset($class->associationMappings[$propName])) { if (isset($class->associationMappings[$propName])) {
$assoc = $class->associationMappings[$propName]; $assoc = $class->associationMappings[$propName];
if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) { if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) {
@ -470,8 +490,9 @@ class UnitOfWork implements PropertyChangedListener
} }
} else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) { } else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) {
// A PersistentCollection was de-referenced, so delete it. // A PersistentCollection was de-referenced, so delete it.
if ( ! in_array($orgValue, $this->collectionDeletions, true)) { $coid = spl_object_hash($orgValue);
$this->collectionDeletions[] = $orgValue; if ( ! isset($this->collectionDeletions[$coid]) ) {
$this->collectionDeletions[$coid] = $orgValue;
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
} }
} }
@ -528,7 +549,7 @@ class UnitOfWork implements PropertyChangedListener
foreach ($entitiesToProcess as $entity) { foreach ($entitiesToProcess as $entity) {
// Ignore uninitialized proxy objects // Ignore uninitialized proxy objects
if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
continue; continue;
} }
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
@ -549,10 +570,11 @@ class UnitOfWork implements PropertyChangedListener
private function computeAssociationChanges($assoc, $value) private function computeAssociationChanges($assoc, $value)
{ {
if ($value instanceof PersistentCollection && $value->isDirty()) { if ($value instanceof PersistentCollection && $value->isDirty()) {
$coid = spl_object_hash($value);
if ($assoc['isOwningSide']) { if ($assoc['isOwningSide']) {
$this->collectionUpdates[] = $value; $this->collectionUpdates[$coid] = $value;
} }
$this->visitedCollections[] = $value; $this->visitedCollections[$coid] = $value;
} }
// Look through the entities, and in any of their associations, for transient (new) // Look through the entities, and in any of their associations, for transient (new)
@ -635,7 +657,7 @@ class UnitOfWork implements PropertyChangedListener
* @param object $entity The entity for which to (re)calculate the change set. * @param object $entity The entity for which to (re)calculate the change set.
* @throws InvalidArgumentException If the passed entity is not MANAGED. * @throws InvalidArgumentException If the passed entity is not MANAGED.
*/ */
public function recomputeSingleEntityChangeSet($class, $entity) public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
@ -834,6 +856,8 @@ class UnitOfWork implements PropertyChangedListener
// See if there are any new classes in the changeset, that are not in the // See if there are any new classes in the changeset, that are not in the
// commit order graph yet (dont have a node). // commit order graph yet (dont have a node).
// TODO: Can we know the know the possible $newNodes based on something more efficient? IdentityMap?
$newNodes = array(); $newNodes = array();
foreach ($entityChangeSet as $oid => $entity) { foreach ($entityChangeSet as $oid => $entity) {
$className = get_class($entity); $className = get_class($entity);
@ -845,12 +869,13 @@ class UnitOfWork implements PropertyChangedListener
} }
// Calculate dependencies for new nodes // Calculate dependencies for new nodes
foreach ($newNodes as $class) { while ($class = array_pop($newNodes)) {
foreach ($class->associationMappings as $assoc) { foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if ( ! $calc->hasClass($targetClass->name)) { if ( ! $calc->hasClass($targetClass->name)) {
$calc->addClass($targetClass); $calc->addClass($targetClass);
$newNodes[] = $targetClass;
} }
$calc->addDependency($targetClass, $class); $calc->addDependency($targetClass, $class);
// If the target class has mapped subclasses, // If the target class has mapped subclasses,
@ -860,6 +885,7 @@ class UnitOfWork implements PropertyChangedListener
$targetSubClass = $this->em->getClassMetadata($subClassName); $targetSubClass = $this->em->getClassMetadata($subClassName);
if ( ! $calc->hasClass($subClassName)) { if ( ! $calc->hasClass($subClassName)) {
$calc->addClass($targetSubClass); $calc->addClass($targetSubClass);
$newNodes[] = $targetSubClass;
} }
$calc->addDependency($targetSubClass, $class); $calc->addDependency($targetSubClass, $class);
} }
@ -1102,6 +1128,22 @@ class UnitOfWork implements PropertyChangedListener
} }
} }
} }
} else if (!$class->idGenerator->isPostInsertGenerator()) {
// if we have a pre insert generator we can't be sure that having an id
// really means that the entity exists. We have to verify this through
// the last resort: a db lookup
// Last try before db lookup: check the identity map.
if ($this->tryGetById($id, $class->rootEntityName)) {
return self::STATE_DETACHED;
} else {
// db lookup
if ($this->getEntityPersister(get_class($entity))->exists($entity)) {
return self::STATE_DETACHED;
} else {
return self::STATE_NEW;
}
}
} else { } else {
return self::STATE_DETACHED; return self::STATE_DETACHED;
} }
@ -1368,6 +1410,10 @@ class UnitOfWork implements PropertyChangedListener
if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) { if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) {
$managedCopy = $entity; $managedCopy = $entity;
} else { } else {
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
$entity->__load();
}
// Try to look the entity up in the identity map. // Try to look the entity up in the identity map.
$id = $class->getIdentifierValues($entity); $id = $class->getIdentifierValues($entity);
@ -1429,11 +1475,17 @@ class UnitOfWork implements PropertyChangedListener
if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) { if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) {
$prop->setValue($managedCopy, $other); $prop->setValue($managedCopy, $other);
} else { } else {
$targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
$id = $targetClass->getIdentifierValues($other); $relatedId = $targetClass->getIdentifierValues($other);
$proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $id);
if ($targetClass->subClasses) {
$entity = $this->em->find($targetClass->name, $relatedId);
} else {
$proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId);
$prop->setValue($managedCopy, $proxy); $prop->setValue($managedCopy, $proxy);
$this->registerManaged($proxy, $id, array()); $this->registerManaged($proxy, $relatedId, array());
}
} }
} }
} else { } else {
@ -1515,8 +1567,9 @@ class UnitOfWork implements PropertyChangedListener
* *
* @param object $entity * @param object $entity
* @param array $visited * @param array $visited
* @param boolean $noCascade if true, don't cascade detach operation
*/ */
private function doDetach($entity, array &$visited) private function doDetach($entity, array &$visited, $noCascade = false)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
if (isset($visited[$oid])) { if (isset($visited[$oid])) {
@ -1539,8 +1592,10 @@ class UnitOfWork implements PropertyChangedListener
return; return;
} }
if (!$noCascade) {
$this->cascadeDetach($entity, $visited); $this->cascadeDetach($entity, $visited);
} }
}
/** /**
* Refreshes the state of the given entity from the database, overwriting * Refreshes the state of the given entity from the database, overwriting
@ -1742,7 +1797,7 @@ class UnitOfWork implements PropertyChangedListener
*/ */
public function lock($entity, $lockMode, $lockVersion = null) public function lock($entity, $lockMode, $lockVersion = null)
{ {
if ($this->getEntityState($entity) != self::STATE_MANAGED) { if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
throw new InvalidArgumentException("Entity is not MANAGED."); throw new InvalidArgumentException("Entity is not MANAGED.");
} }
@ -1790,9 +1845,12 @@ class UnitOfWork implements PropertyChangedListener
/** /**
* Clears the UnitOfWork. * Clears the UnitOfWork.
*
* @param string $entityName if given, only entities of this type will get detached
*/ */
public function clear() public function clear($entityName = null)
{ {
if ($entityName === null) {
$this->identityMap = $this->identityMap =
$this->entityIdentifiers = $this->entityIdentifiers =
$this->originalEntityData = $this->originalEntityData =
@ -1809,9 +1867,19 @@ class UnitOfWork implements PropertyChangedListener
if ($this->commitOrderCalculator !== null) { if ($this->commitOrderCalculator !== null) {
$this->commitOrderCalculator->clear(); $this->commitOrderCalculator->clear();
} }
} else {
$visited = array();
foreach ($this->identityMap as $className => $entities) {
if ($className === $entityName) {
foreach ($entities as $entity) {
$this->doDetach($entity, $visited, true);
}
}
}
}
if ($this->evm->hasListeners(Events::onClear)) { if ($this->evm->hasListeners(Events::onClear)) {
$this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName));
} }
} }
@ -1839,12 +1907,12 @@ class UnitOfWork implements PropertyChangedListener
{ {
//TODO: if $coll is already scheduled for recreation ... what to do? //TODO: if $coll is already scheduled for recreation ... what to do?
// Just remove $coll from the scheduled recreations? // Just remove $coll from the scheduled recreations?
$this->collectionDeletions[] = $coll; $this->collectionDeletions[spl_object_hash($coll)] = $coll;
} }
public function isCollectionScheduledForDeletion(PersistentCollection $coll) public function isCollectionScheduledForDeletion(PersistentCollection $coll)
{ {
return in_array($coll, $this->collectionsDeletions, true); return isset( $this->collectionsDeletions[spl_object_hash($coll)] );
} }
/** /**
@ -2164,9 +2232,11 @@ class UnitOfWork implements PropertyChangedListener
public function tryGetById($id, $rootClassName) public function tryGetById($id, $rootClassName)
{ {
$idHash = implode(' ', (array) $id); $idHash = implode(' ', (array) $id);
if (isset($this->identityMap[$rootClassName][$idHash])) { if (isset($this->identityMap[$rootClassName][$idHash])) {
return $this->identityMap[$rootClassName][$idHash]; return $this->identityMap[$rootClassName][$idHash];
} }
return false; return false;
} }
@ -2382,4 +2452,37 @@ class UnitOfWork implements PropertyChangedListener
{ {
return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj); return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj);
} }
/**
* Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
*
* This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
* on this object that might be necessary to perform a correct udpate.
*
* @throws \InvalidArgumentException
* @param $object
* @return void
*/
public function markReadOnly($object)
{
if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
throw new InvalidArgumentException("Managed entity required");
}
$this->readOnlyObjects[spl_object_hash($object)] = true;
}
/**
* Is this entity read only?
*
* @throws \InvalidArgumentException
* @param $object
* @return void
*/
public function isReadOnly($object)
{
if ( ! is_object($object) ) {
throw new InvalidArgumentException("Managed entity required");
}
return isset($this->readOnlyObjects[spl_object_hash($object)]);
}
} }

@ -1 +1 @@
Subproject commit 74a2c924cd08b30785877808b1fb519b4b2e60b1 Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2

@ -1 +1 @@
Subproject commit be3790059cc43b674a55548eb42d5d25846ea6a9 Subproject commit f91395b6f469b5076f52fefd64574c443b076485

@ -0,0 +1,48 @@
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* CmsEmail
*
* @Entity
* @Table(name="cms_emails")
*/
class CmsEmail
{
/**
* @Column(type="integer")
* @Id @GeneratedValue
*/
public $id;
/**
* @Column(length=250)
*/
public $email;
/**
* @OneToOne(targetEntity="CmsUser", mappedBy="email")
*/
public $user;
public function getId() {
return $this->id;
}
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
public function getUser() {
return $this->user;
}
public function setUser(CmsUser $user) {
$this->user = $user;
}
}

@ -31,11 +31,11 @@ class CmsUser
*/ */
public $name; public $name;
/** /**
* @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true) * @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"persist", "merge"}, orphanRemoval=true)
*/ */
public $phonenumbers; public $phonenumbers;
/** /**
* @OneToMany(targetEntity="CmsArticle", mappedBy="user") * @OneToMany(targetEntity="CmsArticle", mappedBy="user", cascade={"detach"})
*/ */
public $articles; public $articles;
/** /**
@ -43,7 +43,12 @@ class CmsUser
*/ */
public $address; public $address;
/** /**
* @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge"}) * @OneToOne(targetEntity="CmsEmail", inversedBy="user", cascade={"persist"}, orphanRemoval=true)
* @JoinColumn(referencedColumnName="id", nullable=true)
*/
public $email;
/**
* @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge", "detach"})
* @JoinTable(name="cms_users_groups", * @JoinTable(name="cms_users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
@ -119,4 +124,16 @@ class CmsUser
$address->setUser($this); $address->setUser($this);
} }
} }
public function getEmail() { return $this->email; }
public function setEmail(CmsEmail $email = null) {
if ($this->email !== $email) {
$this->email = $email;
if ($email) {
$email->setUser($this);
}
}
}
} }

@ -15,7 +15,7 @@ class CompanyCar
private $id; private $id;
/** /**
* @Column(type="string", length="50") * @Column(type="string", length=50)
*/ */
private $brand; private $brand;

@ -7,7 +7,11 @@ namespace Doctrine\Tests\Models\Company;
* @Table(name="company_contracts") * @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE") * @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"fix" = "CompanyFixContract", "flexible" = "CompanyFlexContract", "flexultra" = "CompanyFlexUltraContract"}) * @DiscriminatorMap({
* "fix" = "CompanyFixContract",
* "flexible" = "CompanyFlexContract",
* "flexultra" = "CompanyFlexUltraContract"
* })
*/ */
abstract class CompanyContract abstract class CompanyContract
{ {

@ -18,6 +18,11 @@ class CompanyEmployee extends CompanyPerson
*/ */
private $department; private $department;
/**
* @Column(type="datetime", nullable=true)
*/
private $startDate;
public function getSalary() { public function getSalary() {
return $this->salary; return $this->salary;
} }
@ -33,4 +38,12 @@ class CompanyEmployee extends CompanyPerson
public function setDepartment($dep) { public function setDepartment($dep) {
$this->department = $dep; $this->department = $dep;
} }
public function getStartDate() {
return $this->startDate;
}
public function setStartDate($date) {
$this->startDate = $date;
}
} }

@ -9,7 +9,7 @@ namespace Doctrine\Tests\Models\Company;
class CompanyManager extends CompanyEmployee class CompanyManager extends CompanyEmployee
{ {
/** /**
* @Column(type="string", length="250") * @Column(type="string", length=250)
*/ */
private $title; private $title;

@ -0,0 +1,36 @@
<?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\DDC753;
use Doctrine\ORM\EntityRepository;
class DDC753CustomRepository extends EntityRepository
{
/**
* @return bool
*/
public function isCustomRepository()
{
return true;
}
}

@ -0,0 +1,35 @@
<?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\DDC753;
use Doctrine\ORM\EntityRepository;
class DDC753DefaultRepository extends EntityRepository
{
/**
* @return bool
*/
public function isDefaultRepository()
{
return true;
}
}

@ -0,0 +1,39 @@
<?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\DDC753;
/**
* @Entity(repositoryClass = "Doctrine\Tests\Models\DDC753\DDC753CustomRepository")
*/
class DDC753EntityWithCustomRepository
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
protected $id;
/** @column(type="string") */
protected $name;
}

@ -0,0 +1,39 @@
<?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\DDC753;
/**
* @Entity()
*/
class DDC753EntityWithDefaultCustomRepository
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
protected $id;
/** @column(type="string") */
protected $name;
}

@ -0,0 +1,39 @@
<?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\DDC753;
/**
* @Entity(repositoryClass = "\stdClass")
*/
class DDC753EntityWithInvalidRepository
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
protected $id;
/** @column(type="string") */
protected $name;
}

@ -0,0 +1,28 @@
<?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\DDC753;
use Doctrine\ORM\EntityRepository;
class DDC753InvalidRepository
{
}

@ -0,0 +1,40 @@
<?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\DDC869;
/**
* @Entity
*/
class DDC869ChequePayment extends DDC869Payment
{
/** @column(type="string") */
protected $serialNumber;
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'fieldName' => 'serialNumber',
'type' => 'string',
));
}
}

@ -0,0 +1,40 @@
<?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\DDC869;
/**
* @Entity
*/
class DDC869CreditCardPayment extends DDC869Payment
{
/** @column(type="string") */
protected $creditCardNumber;
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'fieldName' => 'creditCardNumber',
'type' => 'string',
));
}
}

@ -0,0 +1,57 @@
<?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\DDC869;
/**
* @MappedSuperclass(repositoryClass = "Doctrine\Tests\Models\DDC869\DDC869PaymentRepository")
*/
class DDC869Payment
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
protected $id;
/** @column(type="float") */
protected $value;
public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer',
'columnName' => 'id',
));
$metadata->mapField(array(
'fieldName' => 'value',
'type' => 'float',
));
$metadata->isMappedSuperclass = true;
$metadata->setCustomRepositoryClass("Doctrine\Tests\Models\DDC869\DDC869PaymentRepository");
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadataInfo::GENERATOR_TYPE_AUTO);
}
}

@ -0,0 +1,37 @@
<?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\DDC869;
use Doctrine\ORM\EntityRepository;
class DDC869PaymentRepository extends EntityRepository
{
/**
* Very complex method
*
* @return bool
*/
public function isTrue()
{
return true;
}
}

@ -22,7 +22,7 @@ class ECommerceProduct
private $id; private $id;
/** /**
* @Column(type="string", length=50, nullable="true") * @Column(type="string", length=50, nullable=true)
*/ */
private $name; private $name;

@ -0,0 +1,91 @@
<?php
namespace Doctrine\Tests\ORM\Event;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* @group DDC-1415
*/
class EntityEventDelegatorTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var \Doctrine\ORM\Event\EntityEventDelegator
*/
private $delegator;
public function setUp()
{
$this->delegator = new \Doctrine\ORM\Event\EntityEventDelegator();
}
public function testGetSubscribedEventsWhenEmpty()
{
$this->assertEquals(array(), $this->delegator->getSubscribedEvents());
}
public function testAddListener()
{
$this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener());
$this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents());
}
public function testAddSubscriber()
{
$this->delegator->addEventSubscriber(new DelegateeEventListener(), 'stdClass');
$this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents());
}
public function testAddListenerAfterFrozenThrowsException()
{
$this->delegator->getSubscribedEvents(); // freezes
$this->setExpectedException("LogicException", "Cannot add event listeners aft");
$this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener());
}
public function testDelegateEvent()
{
$delegatee = new DelegateeEventListener();
$this->delegator->addEventListener('postLoad', 'stdClass', $delegatee);
$event = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager());
$this->delegator->postLoad($event);
$this->delegator->postLoad($event);
$this->assertEquals(2, $delegatee->postLoad);
}
public function testDelegatePickEntity()
{
$delegatee = new DelegateeEventListener();
$this->delegator->addEventListener('postLoad', 'stdClass', $delegatee);
$event1 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager());
$event2 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \Doctrine\Tests\Models\CMS\CmsUser(), $this->_getTestEntityManager());
$this->delegator->postLoad($event1);
$this->delegator->postLoad($event2);
$this->assertEquals(1, $delegatee->postLoad);
}
}
class DelegateeEventListener implements \Doctrine\Common\EventSubscriber
{
public $postLoad = 0;
public function postLoad($args)
{
$this->postLoad++;
}
/**
* Returns an array of events this subscriber wants to listen to.
*
* @return array
*/
function getSubscribedEvents()
{
return array('postLoad');
}
}

@ -150,15 +150,15 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user->username = 'gblanco'; $user->username = 'gblanco';
$user->status = 'developer'; $user->status = 'developer';
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user), "State should be UnitOfWork::STATE_NEW");
$this->_em->persist($user); $this->_em->persist($user);
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user), "State should be UnitOfWork::STATE_MANAGED");
$this->_em->remove($user); $this->_em->remove($user);
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user), "State should be UnitOfWork::STATE_NEW");
$this->_em->persist($user); $this->_em->persist($user);
$this->_em->flush(); $this->_em->flush();
@ -166,10 +166,10 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->remove($user); $this->_em->remove($user);
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($user)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($user), "State should be UnitOfWork::STATE_REMOVED");
$this->_em->flush(); $this->_em->flush();
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user)); $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_NEW, $this->_em->getUnitOfWork()->getEntityState($user), "State should be UnitOfWork::STATE_NEW");
$this->assertNull($this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $id)); $this->assertNull($this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $id));
} }
@ -863,7 +863,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt() public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt()
{ {
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
$user = new CmsUser(); $user = new CmsUser();
$user->username = "beberlei"; $user->username = "beberlei";
$user->name = "Benjamin E."; $user->name = "Benjamin E.";
@ -882,7 +881,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
$this->assertEquals('Stephan', $this->_em->find(get_class($user), $userId)->name); $this->assertEquals('Benjamin E.', $this->_em->find(get_class($user), $userId)->name);
} }
public function testMergePersistsNewEntities() public function testMergePersistsNewEntities()
@ -981,4 +980,54 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($article->user->__isInitialized__, "...but its initialized!"); $this->assertTrue($article->user->__isInitialized__, "...but its initialized!");
$this->assertEquals($qc+2, $this->getCurrentQueryCount()); $this->assertEquals($qc+2, $this->getCurrentQueryCount());
} }
/**
* @group DDC-1278
*/
public function testClearWithEntityName()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';
$address = new CmsAddress();
$address->city = "Springfield";
$address->zip = "12354";
$address->country = "Germany";
$address->street = "Foo Street";
$address->user = $user;
$user->address = $address;
$article1 = new CmsArticle();
$article1->topic = 'Foo';
$article1->text = 'Foo Text';
$article2 = new CmsArticle();
$article2->topic = 'Bar';
$article2->text = 'Bar Text';
$user->addArticle($article1);
$user->addArticle($article2);
$this->_em->persist($article1);
$this->_em->persist($article2);
$this->_em->persist($address);
$this->_em->persist($user);
$this->_em->flush();
$unitOfWork = $this->_em->getUnitOfWork();
$this->_em->clear('Doctrine\Tests\Models\CMS\CmsUser');
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($user));
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($address));
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article1));
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article2));
$this->_em->clear();
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address));
}
} }

@ -292,6 +292,19 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue(count($this->_em->createQuery( $this->assertTrue(count($this->_em->createQuery(
'SELECT count(p.id) FROM Doctrine\Tests\Models\Company\CompanyEmployee p WHERE p.salary = 1') 'SELECT count(p.id) FROM Doctrine\Tests\Models\Company\CompanyEmployee p WHERE p.salary = 1')
->getResult()) > 0); ->getResult()) > 0);
}
/**
* @group DDC-1341
*/
public function testBulkUpdateNonScalarParameterDDC1341()
{
$dql = 'UPDATE Doctrine\Tests\Models\Company\CompanyEmployee AS p SET p.startDate = ?0 WHERE p.department = ?1';
$query = $this->_em->createQuery($dql)
->setParameter(0, new \DateTime())
->setParameter(1, 'IT');
$result = $query->execute();
} }

@ -59,7 +59,7 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'select u from Doctrine\Tests\Models\CMS\CmsUser u', 'select u from Doctrine\Tests\Models\CMS\CmsUser u',
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = 1" "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ WHERE c0_.id = 1"
); );
} }
@ -67,7 +67,7 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName', 'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName',
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1" "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1"
); );
} }
@ -75,7 +75,7 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name', 'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name',
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1" "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1"
); );
} }
} }

@ -55,6 +55,29 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Poweruser', $a2->getUser()->type); $this->assertEquals('Poweruser', $a2->getUser()->type);
} }
/**
* @group DDC-1386
*/
public function testGetPartialReferenceWithDefaultValueNotEvalutedInFlush()
{
$user = new DefaultValueUser;
$user->name = 'romanb';
$user->type = 'Normaluser';
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$user = $this->_em->getPartialReference('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id);
$this->assertTrue($this->_em->getUnitOfWork()->isReadOnly($user));
$this->_em->flush();
$this->_em->clear();
$user = $this->_em->find('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id);
$this->assertEquals('Normaluser', $user->type);
}
} }

@ -433,5 +433,43 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertSame($usersAsc[0], $usersDesc[1]); $this->assertSame($usersAsc[0], $usersDesc[1]);
$this->assertSame($usersAsc[1], $usersDesc[0]); $this->assertSame($usersAsc[1], $usersDesc[0]);
} }
/**
* @group DDC-753
*/
public function testDefaultRepositoryClassName()
{
$this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository");
$this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753DefaultRepository");
$this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\Tests\Models\DDC753\DDC753DefaultRepository");
$repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithDefaultCustomRepository');
$this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753DefaultRepository", $repos);
$this->assertTrue($repos->isDefaultRepository());
$repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithCustomRepository');
$this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753CustomRepository", $repos);
$this->assertTrue($repos->isCustomRepository());
$this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\Tests\Models\DDC753\DDC753DefaultRepository");
$this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\ORM\EntityRepository");
$this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository");
}
/**
* @group DDC-753
* @expectedException Doctrine\ORM\ORMException
* @expectedExceptionMessage Invalid repository class 'Doctrine\Tests\Models\DDC753\DDC753InvalidRepository'. it must be a Doctrine\ORM\EntityRepository.
*/
public function testSetDefaultRepositoryInvalidClassError()
{
$this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository");
$this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository");
}
} }

@ -78,7 +78,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id); $reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id);
$this->assertFalse($reference->postLoadCallbackInvoked); $this->assertFalse($reference->postLoadCallbackInvoked);
$reference->getId(); // trigger proxy load $reference->getValue(); // trigger proxy load
$this->assertTrue($reference->postLoadCallbackInvoked); $this->assertTrue($reference->postLoadCallbackInvoked);
} }
@ -210,6 +210,10 @@ class LifecycleCallbackTestEntity
return $this->id; return $this->id;
} }
public function getValue() {
return $this->value;
}
/** @PrePersist */ /** @PrePersist */
public function doStuffOnPrePersist() { public function doStuffOnPrePersist() {
$this->prePersistCallbackInvoked = true; $this->prePersistCallbackInvoked = true;

@ -0,0 +1,58 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsAddress,
Doctrine\Tests\Models\CMS\CmsPhonenumber;
require_once __DIR__ . '/../../TestInit.php';
/**
* Tests a bidirectional one-to-many association mapping with orphan removal.
*/
class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser;
$user->status = 'dev';
$user->username = 'romanb';
$user->name = 'Roman B.';
$phone = new CmsPhonenumber;
$phone->phonenumber = '123456';
$user->addPhonenumber($phone);
$this->_em->persist($user);
$this->_em->flush();
$userId = $user->getId();
$this->_em->clear();
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$this->_em->remove($userProxy);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsUser should be removed by EntityManager');
$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');
}
}

@ -0,0 +1,94 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsEmail,
Doctrine\Tests\Models\CMS\CmsAddress,
Doctrine\Tests\Models\CMS\CmsPhonenumber;
require_once __DIR__ . '/../../TestInit.php';
/**
* Tests a bidirectional one-to-one association mapping with orphan removal.
*/
class OneToOneOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser;
$user->status = 'dev';
$user->username = 'romanb';
$user->name = 'Roman B.';
$address = new CmsAddress;
$address->country = 'de';
$address->zip = 1234;
$address->city = 'Berlin';
$user->setAddress($address);
$this->_em->persist($user);
$this->_em->flush();
$userId = $user->getId();
$this->_em->clear();
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$this->_em->remove($userProxy);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsUser should be removed by EntityManager');
$query = $this->_em->createQuery('SELECT a FROM Doctrine\Tests\Models\CMS\CmsAddress a');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsAddress should be removed by orphanRemoval');
}
public function testOrphanRemovalWhenUnlink()
{
$user = new CmsUser;
$user->status = 'dev';
$user->username = 'beberlei';
$user->name = 'Bejamin Eberlei';
$email = new CmsEmail;
$email->email = 'beberlei@domain.com';
$user->setEmail($email);
$this->_em->persist($user);
$this->_em->flush();
$userId = $user->getId();
$this->_em->clear();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$user->setEmail(null);
$this->_em->persist($user);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmail e');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsEmail should be removed by orphanRemoval');
}
}

@ -14,11 +14,28 @@ require_once __DIR__ . '/../../TestInit.php';
*/ */
class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
/**
* @var \ReflectionProperty
*/
private $cacheDataReflection;
protected function setUp() { protected function setUp() {
$this->cacheDataReflection = new \ReflectionProperty("Doctrine\Common\Cache\ArrayCache", "data");
$this->cacheDataReflection->setAccessible(true);
$this->useModelSet('cms'); $this->useModelSet('cms');
parent::setUp(); parent::setUp();
} }
/**
* @param ArrayCache $cache
* @return integer
*/
private function getCacheSize(ArrayCache $cache)
{
return sizeof($this->cacheDataReflection->getValue($cache));
}
public function testQueryCache_DependsOnHints() public function testQueryCache_DependsOnHints()
{ {
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
@ -27,12 +44,12 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setQueryCacheDriver($cache); $query->setQueryCacheDriver($cache);
$query->getResult(); $query->getResult();
$this->assertEquals(1, count($cache->getIds())); $this->assertEquals(1, $this->getCacheSize($cache));
$query->setHint('foo', 'bar'); $query->setHint('foo', 'bar');
$query->getResult(); $query->getResult();
$this->assertEquals(2, count($cache->getIds())); $this->assertEquals(2, $this->getCacheSize($cache));
return $query; return $query;
} }
@ -44,13 +61,13 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testQueryCache_DependsOnFirstResult($query) public function testQueryCache_DependsOnFirstResult($query)
{ {
$cache = $query->getQueryCacheDriver(); $cache = $query->getQueryCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$query->setFirstResult(10); $query->setFirstResult(10);
$query->setMaxResults(9999); $query->setMaxResults(9999);
$query->getResult(); $query->getResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
/** /**
@ -60,12 +77,12 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testQueryCache_DependsOnMaxResults($query) public function testQueryCache_DependsOnMaxResults($query)
{ {
$cache = $query->getQueryCacheDriver(); $cache = $query->getQueryCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$query->setMaxResults(10); $query->setMaxResults(10);
$query->getResult(); $query->getResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
/** /**
@ -75,10 +92,10 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testQueryCache_DependsOnHydrationMode($query) public function testQueryCache_DependsOnHydrationMode($query)
{ {
$cache = $query->getQueryCacheDriver(); $cache = $query->getQueryCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$query->getArrayResult(); $query->getArrayResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
public function testQueryCache_NoHitSaveParserResult() public function testQueryCache_NoHitSaveParserResult()
@ -87,13 +104,13 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$cache = $this->getMock('Doctrine\Common\Cache\AbstractCache', array('_doFetch', '_doContains', '_doSave', '_doDelete', 'getIds')); $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave'));
$cache->expects($this->at(0)) $cache->expects($this->at(0))
->method('_doFetch') ->method('doFetch')
->with($this->isType('string')) ->with($this->isType('string'))
->will($this->returnValue(false)); ->will($this->returnValue(false));
$cache->expects($this->at(1)) $cache->expects($this->at(1))
->method('_doSave') ->method('doSave')
->with($this->isType('string'), $this->isInstanceOf('Doctrine\ORM\Query\ParserResult'), $this->equalTo(null)); ->with($this->isType('string'), $this->isInstanceOf('Doctrine\ORM\Query\ParserResult'), $this->equalTo(null));
$query->setQueryCacheDriver($cache); $query->setQueryCacheDriver($cache);
@ -117,13 +134,14 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
->method('getSqlExecutor') ->method('getSqlExecutor')
->will($this->returnValue($sqlExecMock)); ->will($this->returnValue($sqlExecMock));
$cache = $this->getMock('Doctrine\Common\Cache\AbstractCache', array('_doFetch', '_doContains', '_doSave', '_doDelete', 'getIds')); $cache = $this->getMock('Doctrine\Common\Cache\CacheProvider',
array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush'));
$cache->expects($this->once()) $cache->expects($this->once())
->method('_doFetch') ->method('doFetch')
->with($this->isType('string')) ->with($this->isType('string'))
->will($this->returnValue($parserResultMock)); ->will($this->returnValue($parserResultMock));
$cache->expects($this->never()) $cache->expects($this->never())
->method('_doSave'); ->method('doSave');
$query->setQueryCacheDriver($cache); $query->setQueryCacheDriver($cache);

@ -501,4 +501,66 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, count($users)); $this->assertEquals(0, count($users));
} }
public function testQueryWithArrayOfEntitiesAsParameter()
{
$userA = new CmsUser;
$userA->name = 'Benjamin';
$userA->username = 'beberlei';
$userA->status = 'developer';
$this->_em->persist($userA);
$userB = new CmsUser;
$userB->name = 'Roman';
$userB->username = 'romanb';
$userB->status = 'developer';
$this->_em->persist($userB);
$userC = new CmsUser;
$userC->name = 'Jonathan';
$userC->username = 'jwage';
$userC->status = 'developer';
$this->_em->persist($userC);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u IN (?0) OR u.username = ?1");
$query->setParameter(0, array($userA, $userC));
$query->setParameter(1, 'beberlei');
$users = $query->execute();
$this->assertEquals(2, count($users));
}
public function testQueryWithHiddenAsSelectExpression()
{
$userA = new CmsUser;
$userA->name = 'Benjamin';
$userA->username = 'beberlei';
$userA->status = 'developer';
$this->_em->persist($userA);
$userB = new CmsUser;
$userB->name = 'Roman';
$userB->username = 'romanb';
$userB->status = 'developer';
$this->_em->persist($userB);
$userC = new CmsUser;
$userC->name = 'Jonathan';
$userC->username = 'jwage';
$userC->status = 'developer';
$this->_em->persist($userC);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("SELECT u, (SELECT COUNT(u2.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) AS HIDDEN total FROM Doctrine\Tests\Models\CMS\CmsUser u");
$users = $query->execute();
$this->assertEquals(3, count($users));
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]);
}
} }

@ -147,4 +147,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($entity->wakeUp, "Loading the proxy should call __wakeup()."); $this->assertTrue($entity->wakeUp, "Loading the proxy should call __wakeup().");
} }
public function testDoNotInitializeProxyOnGettingTheIdentifier()
{
$id = $this->createProduct();
/* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
$entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
$this->assertEquals($id, $entity->getId());
$this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy.");
}
public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier()
{
$id = $this->createProduct();
/* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
$entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
$this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy.");
$this->assertEquals('Doctrine Cookbook', $entity->getName());
$this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy.");
}
} }

@ -15,11 +15,27 @@ require_once __DIR__ . '/../../TestInit.php';
*/ */
class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
/**
* @var \ReflectionProperty
*/
private $cacheDataReflection;
protected function setUp() { protected function setUp() {
$this->cacheDataReflection = new \ReflectionProperty("Doctrine\Common\Cache\ArrayCache", "data");
$this->cacheDataReflection->setAccessible(true);
$this->useModelSet('cms'); $this->useModelSet('cms');
parent::setUp(); parent::setUp();
} }
/**
* @param ArrayCache $cache
* @return integer
*/
private function getCacheSize(ArrayCache $cache)
{
return sizeof($this->cacheDataReflection->getValue($cache));
}
public function testResultCache() public function testResultCache()
{ {
$user = new CmsUser; $user = new CmsUser;
@ -125,9 +141,9 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$cache = new ArrayCache(); $cache = new ArrayCache();
$query->setResultCacheDriver($cache)->useResultCache(true); $query->setResultCacheDriver($cache)->useResultCache(true);
$this->assertEquals(0, count($cache->getIds())); $this->assertEquals(0, $this->getCacheSize($cache));
$query->getResult(); $query->getResult();
$this->assertEquals(1, count($cache->getIds())); $this->assertEquals(1, $this->getCacheSize($cache));
return $query; return $query;
} }
@ -139,12 +155,12 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testResultCacheDependsOnQueryHints($query) public function testResultCacheDependsOnQueryHints($query)
{ {
$cache = $query->getResultCacheDriver(); $cache = $query->getResultCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$query->setHint('foo', 'bar'); $query->setHint('foo', 'bar');
$query->getResult(); $query->getResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
/** /**
@ -154,12 +170,12 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testResultCacheDependsOnParameters($query) public function testResultCacheDependsOnParameters($query)
{ {
$cache = $query->getResultCacheDriver(); $cache = $query->getResultCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$query->setParameter(1, 50); $query->setParameter(1, 50);
$query->getResult(); $query->getResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
/** /**
@ -169,12 +185,12 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testResultCacheDependsOnHydrationMode($query) public function testResultCacheDependsOnHydrationMode($query)
{ {
$cache = $query->getResultCacheDriver(); $cache = $query->getResultCacheDriver();
$cacheCount = count($cache->getIds()); $cacheCount = $this->getCacheSize($cache);
$this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode()); $this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode());
$query->getArrayResult(); $query->getArrayResult();
$this->assertEquals($cacheCount + 1, count($cache->getIds())); $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
} }
/** /**

@ -0,0 +1,99 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
require_once __DIR__ . '/../../../TestInit.php';
/**
* @group DDC-1113
* @group DDC-1306
*/
class DDC1113Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1113Engine'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1113Vehicle'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1113Car'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1113Bus'),
));
} catch (\Exception $e) {
}
}
public function testIssue()
{
$car = new DDC1113Car();
$car->engine = new DDC1113Engine();
$bus = new DDC1113Bus();
$bus->engine = new DDC1113Engine();
$this->_em->persist($car);
$this->_em->flush();
$this->_em->persist($bus);
$this->_em->flush();
$this->_em->remove($bus);
$this->_em->remove($car);
$this->_em->flush();
}
}
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorMap({"car" = "DDC1113Car", "bus" = "DDC1113Bus"})
*/
class DDC1113Vehicle
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
/**
* @ManyToOne(targetEntity="DDC1113Vehicle")
*/
public $parent;
/** @OneToOne(targetEntity="DDC1113Engine", cascade={"persist", "remove"}) */
public $engine;
}
/**
* @Entity
*/
class DDC1113Car extends DDC1113Vehicle
{
}
/**
* @Entity
*/
class DDC1113Bus extends DDC1113Vehicle
{
}
/**
* @Entity
*/
class DDC1113Engine
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
}

@ -52,6 +52,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$this->_em->clear(); $this->_em->clear();
// force proxy load, getId() doesn't work anymore
$user->getName();
$userId = $user->getId(); $userId = $user->getId();
$this->_em->clear(); $this->_em->clear();
@ -60,6 +62,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase
$user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId);
// force proxy load, getId() doesn't work anymore
$user->getName();
$this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type"); $this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type");
} }
} }

Some files were not shown because too many files have changed in this diff Show More