Initial support for scalar hydration (HYDRATE_SCALAR). EntityPersisters born.
This commit is contained in:
parent
613d08f9ca
commit
d0ea5705d0
@ -473,11 +473,11 @@ final class Doctrine
|
||||
const HYDRATE_NONE = 4;
|
||||
|
||||
/* new hydration modes. move to Query class when it's time. */
|
||||
//const HYDRATE_IDENTITY_OBJECT = 1; // default, auto-adds PKs, produces object graphs
|
||||
//const HYDRATE_IDENTITY_ARRAY = 2; // auto-adds PKs, produces array graphs
|
||||
//const HYDRATE_SCALAR = 3; // produces flat result list with scalar values
|
||||
//const HYDRATE_SINGLE_SCALAR = 4; // produces a single scalar value
|
||||
//const HYDRATE_NONE = 5; // produces a result set as it's returned by the db
|
||||
const HYDRATE_IDENTITY_OBJECT = 2; // default, auto-adds PKs, produces object graphs
|
||||
const HYDRATE_IDENTITY_ARRAY = 3; // auto-adds PKs, produces array graphs
|
||||
const HYDRATE_SCALAR = 5; // produces flat result list with scalar values
|
||||
const HYDRATE_SINGLE_SCALAR = 6; // produces a single scalar value
|
||||
//const HYDRATE_NONE = 4; // produces a result set as it's returned by the db
|
||||
|
||||
|
||||
/**
|
||||
|
@ -200,8 +200,9 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
|
||||
public function __construct($adapter, $user = null, $pass = null)
|
||||
{
|
||||
if (is_object($adapter)) {
|
||||
if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) {
|
||||
throw new Doctrine_Connection_Exception('First argument should be an instance of PDO or implement Doctrine_Adapter_Interface');
|
||||
if ( ! $adapter instanceof PDO) {
|
||||
throw new Doctrine_Connection_Exception(
|
||||
'First argument should be an instance of PDO or implement Doctrine_Adapter_Interface');
|
||||
}
|
||||
$this->dbh = $adapter;
|
||||
$this->isConnected = true;
|
||||
@ -216,12 +217,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
|
||||
if (isset($adapter['other'])) {
|
||||
$this->options['other'] = array(Doctrine::ATTR_PERSISTENT => $adapter['persistent']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->setAttribute(Doctrine::ATTR_CASE, Doctrine::CASE_NATURAL);
|
||||
$this->setAttribute(Doctrine::ATTR_ERRMODE, Doctrine::ERRMODE_EXCEPTION);
|
||||
$this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this);
|
||||
}
|
||||
|
||||
|
||||
@ -327,26 +323,16 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
|
||||
$this->getListener()->preConnect($event);
|
||||
|
||||
$e = explode(':', $this->options['dsn']);
|
||||
$found = false;
|
||||
|
||||
if (extension_loaded('pdo')) {
|
||||
if (in_array($e[0], PDO::getAvailableDrivers())) {
|
||||
$this->dbh = new PDO($this->options['dsn'], $this->options['username'],
|
||||
$this->options['password'], $this->options['other']);
|
||||
|
||||
$this->dbh = new PDO(
|
||||
$this->options['dsn'], $this->options['username'],
|
||||
$this->options['password'], $this->options['other']);
|
||||
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $found) {
|
||||
$class = 'Doctrine_Adapter_' . ucwords($e[0]);
|
||||
|
||||
if (class_exists($class)) {
|
||||
$this->dbh = new $class($this->options['dsn'], $this->options['username'], $this->options['password']);
|
||||
} else {
|
||||
throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);
|
||||
$this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
|
||||
}
|
||||
} else {
|
||||
throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);
|
||||
}
|
||||
|
||||
// attach the pending attributes to adapter
|
||||
|
551
lib/Doctrine/EntityManager.php
Normal file
551
lib/Doctrine/EntityManager.php
Normal file
@ -0,0 +1,551 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.phpdoctrine.org>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The EntityManager is a central access point to ORM functionality.
|
||||
*
|
||||
* @package Doctrine
|
||||
* @subpackage EntityManager
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @todo package:orm
|
||||
*/
|
||||
class Doctrine_EntityManager
|
||||
{
|
||||
/**
|
||||
* The unique name of the EntityManager. The name is used to bind entity classes
|
||||
* to certain EntityManagers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_name;
|
||||
|
||||
/**
|
||||
* The database connection used by the EntityManager.
|
||||
*
|
||||
* @var Doctrine_Connection
|
||||
*/
|
||||
private $_conn;
|
||||
|
||||
/**
|
||||
* Flush modes enumeration.
|
||||
*/
|
||||
private static $_flushModes = array(
|
||||
// auto: Flush occurs automatically after each operation that issues database
|
||||
// queries. No operations are queued.
|
||||
'auto',
|
||||
// commit: Flush occurs automatically at transaction commit.
|
||||
'commit',
|
||||
// manual: Flush occurs never automatically.
|
||||
'manual'
|
||||
);
|
||||
|
||||
/**
|
||||
* The metadata factory, used to retrieve the metadata of entity classes.
|
||||
*
|
||||
* @var Doctrine_ClassMetadata_Factory
|
||||
*/
|
||||
private $_metadataFactory;
|
||||
|
||||
/**
|
||||
* The EntityPersister instances.
|
||||
* @todo Implementation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_persisters = array();
|
||||
|
||||
/**
|
||||
* The EntityRepository instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_repositories = array();
|
||||
|
||||
/**
|
||||
* The currently used flush mode. Defaults to 'commit'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_flushMode = 'commit';
|
||||
|
||||
/**
|
||||
* Map of all EntityManagers, keys are the names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $_ems = array();
|
||||
|
||||
/**
|
||||
* EntityManager to Entity bindings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $_emBindings = array();
|
||||
|
||||
/**
|
||||
* The unit of work.
|
||||
*
|
||||
* @var UnitOfWork
|
||||
*/
|
||||
private $_unitOfWork;
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @var unknown_type
|
||||
*/
|
||||
//private $_dataTemplates = array();
|
||||
|
||||
/**
|
||||
* Creates a new EntityManager that operates on the given database connection.
|
||||
*
|
||||
* @param Doctrine_Connection $conn
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Doctrine_Connection $conn, $name = null)
|
||||
{
|
||||
$this->_conn = $conn;
|
||||
$this->_name = $name;
|
||||
$this->_metadataFactory = new Doctrine_ClassMetadata_Factory($this,
|
||||
new Doctrine_ClassMetadata_CodeDriver());
|
||||
$this->_unitOfWork = new Doctrine_Connection_UnitOfWork($conn);
|
||||
if ($name !== null) {
|
||||
self::$_ems[$name] = $this;
|
||||
} else {
|
||||
self::$_ems[] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EntityManager that is responsible for the Entity.
|
||||
*
|
||||
* @param string $entityName
|
||||
* @return EntityManager
|
||||
* @throws Doctrine_EntityManager_Exception If a suitable manager can not be found.
|
||||
*/
|
||||
public static function getManager($entityName = null)
|
||||
{
|
||||
if ( ! is_null($entityName) && isset(self::$_emBindings[$entityName])) {
|
||||
$emName = self::$_emBindings[$entityName];
|
||||
if (isset(self::$_ems[$emName])) {
|
||||
return self::$_ems[$emName];
|
||||
} else {
|
||||
throw Doctrine_EntityManager_Exception::noManagerWithName($emName);
|
||||
}
|
||||
} else if (self::$_ems) {
|
||||
return current(self::$_ems);
|
||||
} else {
|
||||
throw Doctrine_EntityManager_Exception::noEntityManagerAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @param unknown_type $entityName
|
||||
* @param unknown_type $emName
|
||||
*/
|
||||
public static function bindEntityToManager($entityName, $emName)
|
||||
{
|
||||
if (isset(self::$_emBindings[$entityName])) {
|
||||
throw Doctrine_EntityManager_Exception::entityAlreadyBound($entityName);
|
||||
}
|
||||
self::$_emBindings[$entityName] = $emName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all bindings between Entities and EntityManagers.
|
||||
*/
|
||||
public static function unbindAllManagers()
|
||||
{
|
||||
self::$_emBindings = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all EntityManagers.
|
||||
*
|
||||
*/
|
||||
public static function releaseAllManagers()
|
||||
{
|
||||
self::$_ems = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the database connection object used by the EntityManager.
|
||||
*
|
||||
* @return Doctrine_Connection
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->_conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata for a class. Alias for getClassMetadata().
|
||||
*
|
||||
* @return Doctrine_Metadata
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function getMetadata($className)
|
||||
{
|
||||
return $this->getClassMetadata($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata for a class.
|
||||
*
|
||||
* @return Doctrine_Metadata
|
||||
*/
|
||||
public function getClassMetadata($className)
|
||||
{
|
||||
return $this->_metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the driver that is used to obtain metadata informations about entity
|
||||
* classes.
|
||||
*
|
||||
* @param $driver The driver to use.
|
||||
*/
|
||||
public function setClassMetadataDriver($driver)
|
||||
{
|
||||
$this->_metadataFactory->setDriver($driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Doctrine_Query object that operates on this connection.
|
||||
*
|
||||
* @return Doctrine_Query
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function createQuery($dql = "")
|
||||
{
|
||||
$query = new Doctrine_Query($this);
|
||||
if ( ! empty($dql)) {
|
||||
$query->parseQuery($dql);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @param unknown_type $entityName
|
||||
* @return unknown
|
||||
*/
|
||||
public function getEntityPersister($entityName)
|
||||
{
|
||||
if ( ! isset($this->_persisters[$entityName])) {
|
||||
$class = $this->getClassMetadata($entityName);
|
||||
if ($class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED) {
|
||||
$persister = new Doctrine_EntityPersister_JoinedSubclass($this, $class);
|
||||
} else {
|
||||
$persister = new Doctrine_EntityPersister_Standard($this, $class);
|
||||
}
|
||||
$this->_persisters[$entityName] = $persister;
|
||||
}
|
||||
return $this->_persisters[$entityName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches an entity from the manager. It's lifecycle is no longer managed.
|
||||
*
|
||||
* @param Doctrine_Entity $entity
|
||||
* @return unknown
|
||||
*/
|
||||
public function detach(Doctrine_Entity $entity)
|
||||
{
|
||||
return $this->_unitOfWork->unregisterIdentity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current internal transaction nesting level.
|
||||
*
|
||||
* @return integer The nesting level. A value of 0 means theres no active transaction.
|
||||
* @todo package:orm???
|
||||
*/
|
||||
public function getInternalTransactionLevel()
|
||||
{
|
||||
return $this->transaction->getInternalTransactionLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a transaction.
|
||||
*
|
||||
* This method must only be used by Doctrine itself to initiate transactions.
|
||||
* Userland-code must use {@link beginTransaction()}.
|
||||
*
|
||||
* @todo package:orm???
|
||||
*/
|
||||
public function beginInternalTransaction($savepoint = null)
|
||||
{
|
||||
return $this->transaction->beginInternalTransaction($savepoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query with the specified name.
|
||||
*
|
||||
* @todo Implementation.
|
||||
* @throws SomeException If there is no query registered with the given name.
|
||||
*/
|
||||
public function createNamedQuery($name)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Implementation.
|
||||
*/
|
||||
public function createNativeQuery($sql = "")
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Implementation.
|
||||
*/
|
||||
public function createNamedNativeQuery($name)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Implementation.
|
||||
*/
|
||||
public function createCriteria()
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all changes to objects that have been queued up to now to the database.
|
||||
*
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$this->beginInternalTransaction();
|
||||
$this->_unitOfWork->flush();
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flush mode.
|
||||
*
|
||||
* @param string $flushMode
|
||||
*/
|
||||
public function setFlushMode($flushMode)
|
||||
{
|
||||
if ( ! in_array($flushMode, self::$_flushModes)) {
|
||||
throw Doctrine_EntityManager_Exception::invalidFlushMode();
|
||||
}
|
||||
$this->_flushMode = $flushMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently used flush mode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFlushMode()
|
||||
{
|
||||
return $this->_flushMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the persistence context, detaching all entities.
|
||||
*
|
||||
* @return void
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
if ($entityName === null) {
|
||||
$this->_unitOfWork->detachAll();
|
||||
foreach ($this->_mappers as $mapper) {
|
||||
$mapper->clear(); // clear identity map of each mapper
|
||||
}
|
||||
} else {
|
||||
$this->getMapper($entityName)->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the EntityManager.
|
||||
*
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* getResultCacheDriver
|
||||
*
|
||||
* @return Doctrine_Cache_Interface
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function getResultCacheDriver()
|
||||
{
|
||||
if ( ! $this->getAttribute(Doctrine::ATTR_RESULT_CACHE)) {
|
||||
throw new Doctrine_Exception('Result Cache driver not initialized.');
|
||||
}
|
||||
|
||||
return $this->getAttribute(Doctrine::ATTR_RESULT_CACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
* getQueryCacheDriver
|
||||
*
|
||||
* @return Doctrine_Cache_Interface
|
||||
* @todo package:orm
|
||||
*/
|
||||
public function getQueryCacheDriver()
|
||||
{
|
||||
if ( ! $this->getAttribute(Doctrine::ATTR_QUERY_CACHE)) {
|
||||
throw new Doctrine_Exception('Query Cache driver not initialized.');
|
||||
}
|
||||
|
||||
return $this->getAttribute(Doctrine::ATTR_QUERY_CACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given entity, persisting it's state.
|
||||
*/
|
||||
public function save(Doctrine_Entity $entity)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given entity from the persistent store.
|
||||
*/
|
||||
public function delete(Doctrine_Entity $entity)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository for the given entity name.
|
||||
*
|
||||
* @return Doctrine_EntityRepository The repository.
|
||||
* @todo Implementation.
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
if (isset($this->_repositories[$entityName])) {
|
||||
return $this->_repositories[$entityName];
|
||||
}
|
||||
|
||||
$metadata = $this->getClassMetadata($entityName);
|
||||
$customRepositoryClassName = $metadata->getCustomRepositoryClass();
|
||||
if ($customRepositoryClassName !== null) {
|
||||
$repository = new $customRepositoryClassName($entityName, $metadata);
|
||||
} else {
|
||||
$repository = new Doctrine_EntityRepository($entityName, $metadata);
|
||||
}
|
||||
$this->_repositories[$entityName] = $repository;
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity. Used to reconstitution as well as new creation.
|
||||
*
|
||||
* @param
|
||||
* @param
|
||||
* @return Doctrine_Entity
|
||||
*/
|
||||
public function createEntity($className, array $data)
|
||||
{
|
||||
$className = $this->_getClassnameToReturn($data, $className);
|
||||
$classMetadata = $this->getClassMetadata($className);
|
||||
if ( ! empty($data)) {
|
||||
$identifierFieldNames = $classMetadata->getIdentifier();
|
||||
$isNew = false;
|
||||
foreach ($identifierFieldNames as $fieldName) {
|
||||
if ( ! isset($data[$fieldName])) {
|
||||
// id field not found return new entity
|
||||
$isNew = true;
|
||||
break;
|
||||
}
|
||||
$id[] = $data[$fieldName];
|
||||
}
|
||||
if ($isNew) {
|
||||
return new $className(true, $data);
|
||||
}
|
||||
|
||||
$idHash = $this->_unitOfWork->getIdentifierHash($id);
|
||||
|
||||
if ($entity = $this->_unitOfWork->tryGetByIdHash($idHash,
|
||||
$classMetadata->getRootClassName())) {
|
||||
return $entity;
|
||||
} else {
|
||||
$entity = new $className(false, $data);
|
||||
$this->_unitOfWork->registerIdentity($entity);
|
||||
}
|
||||
$data = array();
|
||||
} else {
|
||||
$entity = new $className(true, $data);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the dataset for a discriminator column to determine the correct
|
||||
* class to instantiate. If no discriminator column is found, the given
|
||||
* classname will be returned.
|
||||
*
|
||||
* @return string The name of the class to instantiate.
|
||||
* @todo Can be optimized performance-wise.
|
||||
* @todo Move to EntityManager::createEntity()
|
||||
*/
|
||||
private function _getClassnameToReturn(array $data, $className)
|
||||
{
|
||||
$class = $this->getClassMetadata($className);
|
||||
|
||||
$discCol = $class->getInheritanceOption('discriminatorColumn');
|
||||
if ( ! $discCol) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
$discMap = $class->getInheritanceOption('discriminatorMap');
|
||||
|
||||
if (isset($data[$discCol], $discMap[$data[$discCol]])) {
|
||||
return $discMap[$data[$discCol]];
|
||||
} else {
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
|
||||
public function getUnitOfWork()
|
||||
{
|
||||
return $this->_unitOfWork;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
24
lib/Doctrine/EntityManager/Exception.php
Normal file
24
lib/Doctrine/EntityManager/Exception.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class Doctrine_EntityManager_Exception extends Doctrine_Exception
|
||||
{
|
||||
public static function invalidFlushMode()
|
||||
{
|
||||
return new self("Invalid flush mode.");
|
||||
}
|
||||
|
||||
public static function noEntityManagerAvailable()
|
||||
{
|
||||
return new self("No EntityManager available.");
|
||||
}
|
||||
|
||||
public static function entityAlreadyBound($entityName)
|
||||
{
|
||||
return new self("The entity '$entityName' is already bound.");
|
||||
}
|
||||
|
||||
public static function noManagerWithName($emName)
|
||||
{
|
||||
return new self("EntityManager named '$emName' not found.");
|
||||
}
|
||||
}
|
827
lib/Doctrine/EntityPersister/Abstract.php
Normal file
827
lib/Doctrine/EntityPersister/Abstract.php
Normal file
@ -0,0 +1,827 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.phpdoctrine.org>.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @package Doctrine
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @version $Revision: 3406 $
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 2.0
|
||||
*/
|
||||
abstract class Doctrine_EntityPersister_Abstract
|
||||
{
|
||||
/**
|
||||
* The names of all the fields that are available on entities created by this mapper.
|
||||
*/
|
||||
protected $_fieldNames = array();
|
||||
|
||||
/**
|
||||
* Metadata object that descibes the mapping of the mapped entity class.
|
||||
*
|
||||
* @var Doctrine_ClassMetadata
|
||||
*/
|
||||
protected $_classMetadata;
|
||||
|
||||
/**
|
||||
* The name of the domain class this mapper is used for.
|
||||
*/
|
||||
protected $_domainClassName;
|
||||
|
||||
/**
|
||||
* The Doctrine_Connection object that the database connection of this mapper.
|
||||
*
|
||||
* @var Doctrine_Connection $conn
|
||||
*/
|
||||
protected $_conn;
|
||||
|
||||
/**
|
||||
* The EntityManager.
|
||||
*
|
||||
* @var unknown_type
|
||||
*/
|
||||
protected $_em;
|
||||
|
||||
/**
|
||||
* The concrete mapping strategy that is used.
|
||||
*/
|
||||
protected $_mappingStrategy;
|
||||
|
||||
/**
|
||||
* Null object.
|
||||
*/
|
||||
private $_nullObject;
|
||||
|
||||
/**
|
||||
* A list of registered entity listeners.
|
||||
*/
|
||||
private $_entityListeners = array();
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @var unknown_type
|
||||
* @todo To EntityManager.
|
||||
*/
|
||||
private $_dataTemplate = array();
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new mapper.
|
||||
*
|
||||
* @param string $name The name of the domain class this mapper is used for.
|
||||
* @param Doctrine_Table $table The table object used for the mapping procedure.
|
||||
* @throws Doctrine_Connection_Exception if there are no opened connections
|
||||
*/
|
||||
public function __construct(Doctrine_EntityManager $em, Doctrine_ClassMetadata $classMetadata)
|
||||
{
|
||||
$this->_em = $em;
|
||||
$this->_domainClassName = $classMetadata->getClassName();
|
||||
$this->_conn = $classMetadata->getConnection();
|
||||
$this->_classMetadata = $classMetadata;
|
||||
$this->_nullObject = Doctrine_Null::$INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes that the keys of the given field array are field names and converts
|
||||
* them to column names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _convertFieldToColumnNames(array $fields, Doctrine_ClassMetadata $class)
|
||||
{
|
||||
$converted = array();
|
||||
foreach ($fields as $fieldName => $value) {
|
||||
$converted[$class->getColumnName($fieldName)] = $value;
|
||||
}
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes all related composites
|
||||
* this method is always called internally when a record is deleted
|
||||
*
|
||||
* @throws PDOException if something went wrong at database level
|
||||
* @return void
|
||||
*/
|
||||
protected function _deleteComposites(Doctrine_Entity $record)
|
||||
{
|
||||
$classMetadata = $this->_classMetadata;
|
||||
foreach ($classMetadata->getRelations() as $fk) {
|
||||
if ($fk->isComposite()) {
|
||||
$obj = $record->get($fk->getAlias());
|
||||
if ($obj instanceof Doctrine_Entity &&
|
||||
$obj->state() != Doctrine_Entity::STATE_LOCKED) {
|
||||
$obj->delete($this->_mapper->getConnection());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the connection for this class
|
||||
*
|
||||
* @params Doctrine_Connection a connection object
|
||||
* @return Doctrine_Table this object
|
||||
* @todo refactor
|
||||
*/
|
||||
/*public function setConnection(Doctrine_Connection $conn)
|
||||
{
|
||||
$this->_conn = $conn;
|
||||
return $this;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Returns the connection the mapper is currently using.
|
||||
*
|
||||
* @return Doctrine_Connection|null The connection object.
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->_conn;
|
||||
}
|
||||
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->_em;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new record
|
||||
*
|
||||
* @param $array an array where keys are field names and
|
||||
* values representing field values
|
||||
* @return Doctrine_Entity the created record object
|
||||
* @todo To EntityManager.
|
||||
*/
|
||||
public function create(array $array = array())
|
||||
{
|
||||
$record = new $this->_domainClassName();
|
||||
$record->fromArray($array);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function addEntityListener(Doctrine_Record_Listener $listener)
|
||||
{
|
||||
if ( ! in_array($listener, $this->_entityListeners)) {
|
||||
$this->_entityListeners[] = $listener;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function removeEntityListener(Doctrine_Record_Listener $listener)
|
||||
{
|
||||
if ($key = array_search($listener, $this->_entityListeners, true)) {
|
||||
unset($this->_entityListeners[$key]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function notifyEntityListeners(Doctrine_Entity $entity, $callback, $eventType)
|
||||
{
|
||||
if ($this->_entityListeners) {
|
||||
$event = new Doctrine_Event($entity, $eventType);
|
||||
foreach ($this->_entityListeners as $listener) {
|
||||
$listener->$callback($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @param Doctrine_Entity $entity
|
||||
* @return unknown
|
||||
* @todo To EntityManager
|
||||
*/
|
||||
public function detach(Doctrine_Entity $entity)
|
||||
{
|
||||
return $this->_conn->getUnitOfWork()->detach($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* clear
|
||||
* clears the first level cache (identityMap)
|
||||
*
|
||||
* @return void
|
||||
* @todo what about a more descriptive name? clearIdentityMap?
|
||||
* @todo To EntityManager
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->_conn->getUnitOfWork()->clearIdentitiesForEntity($this->_classMetadata->getRootClassName());
|
||||
}
|
||||
|
||||
/**
|
||||
* addRecord
|
||||
* adds a record to identity map
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be added
|
||||
* @return boolean
|
||||
* @todo Better name? registerRecord? Move elsewhere to the new location of the identity maps.
|
||||
* @todo Remove.
|
||||
*/
|
||||
public function addRecord(Doctrine_Entity $record)
|
||||
{
|
||||
if ($this->_conn->unitOfWork->contains($record)) {
|
||||
return false;
|
||||
}
|
||||
$this->_conn->unitOfWork->registerIdentity($record);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the mapper to manage the entity if it's not already managed.
|
||||
*
|
||||
* @return boolean TRUE if the entity was previously not managed and is now managed,
|
||||
* FALSE otherwise (the entity is already managed).
|
||||
* @todo Remove.
|
||||
*/
|
||||
public function manage(Doctrine_Entity $record)
|
||||
{
|
||||
return $this->_conn->unitOfWork->manage($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* removeRecord
|
||||
* removes a record from the identity map, returning true if the record
|
||||
* was found and removed and false if the record wasn't found.
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be removed
|
||||
* @return boolean
|
||||
* @todo Move elsewhere to the new location of the identity maps.
|
||||
*/
|
||||
public function removeRecord(Doctrine_Entity $record)
|
||||
{
|
||||
if ($this->_conn->unitOfWork->contains($record)) {
|
||||
$this->_conn->unitOfWork->unregisterIdentity($record);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getRecord
|
||||
* First checks if record exists in identityMap, if not
|
||||
* returns a new record.
|
||||
*
|
||||
* @return Doctrine_Entity
|
||||
* @todo To EntityManager.
|
||||
*/
|
||||
public function getRecord(array $data)
|
||||
{
|
||||
if ( ! empty($data)) {
|
||||
$identifierFieldNames = $this->_classMetadata->getIdentifier();
|
||||
|
||||
$found = false;
|
||||
foreach ($identifierFieldNames as $fieldName) {
|
||||
if ( ! isset($data[$fieldName])) {
|
||||
// primary key column not found return new record
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
$id[] = $data[$fieldName];
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
return new $this->_domainClassName(true, $data);
|
||||
}
|
||||
|
||||
$idHash = $this->_conn->unitOfWork->getIdentifierHash($id);
|
||||
|
||||
if ($record = $this->_conn->unitOfWork->tryGetByIdHash($idHash,
|
||||
$this->_classMetadata->getRootClassName())) {
|
||||
$record->hydrate($data);
|
||||
} else {
|
||||
$record = new $this->_domainClassName(false, $data);
|
||||
$this->_conn->unitOfWork->registerIdentity($record);
|
||||
}
|
||||
$data = array();
|
||||
} else {
|
||||
$record = new $this->_domainClassName(true, $data);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id database row id
|
||||
* @todo Looks broken. Figure out an implementation and decide whether its needed.
|
||||
*/
|
||||
final public function getProxy($id = null)
|
||||
{
|
||||
if ($id !== null) {
|
||||
$identifierColumnNames = $this->_classMetadata->getIdentifierColumnNames();
|
||||
$query = 'SELECT ' . implode(', ', $identifierColumnNames)
|
||||
. ' FROM ' . $this->_classMetadata->getTableName()
|
||||
. ' WHERE ' . implode(' = ? && ', $identifierColumnNames) . ' = ?';
|
||||
$query = $this->applyInheritance($query);
|
||||
|
||||
$params = array_merge(array($id),array());
|
||||
|
||||
$data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($data === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getRecord($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* applyInheritance
|
||||
* @param $where query where part to be modified
|
||||
* @return string query where part with column aggregation inheritance added
|
||||
* @todo What to do with this? Remove if possible.
|
||||
*/
|
||||
final public function applyInheritance($where)
|
||||
{
|
||||
$discCol = $this->_classMetadata->getInheritanceOption('discriminatorColumn');
|
||||
if ( ! $discCol) {
|
||||
return $where;
|
||||
}
|
||||
|
||||
$discMap = $this->_classMetadata->getInheritanceOption('discriminatorMap');
|
||||
$inheritanceMap = array($discCol => array_search($this->_domainClassName, $discMap));
|
||||
if ( ! empty($inheritanceMap)) {
|
||||
$a = array();
|
||||
foreach ($inheritanceMap as $column => $value) {
|
||||
$a[] = $column . ' = ?';
|
||||
}
|
||||
$i = implode(' AND ', $a);
|
||||
$where .= ' AND ' . $i;
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* prepareValue
|
||||
* this method performs special data preparation depending on
|
||||
* the type of the given column
|
||||
*
|
||||
* 1. It unserializes array and object typed columns
|
||||
* 2. Uncompresses gzip typed columns
|
||||
* 3. Gets the appropriate enum values for enum typed columns
|
||||
* 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
|
||||
*
|
||||
* example:
|
||||
* <code type='php'>
|
||||
* $field = 'name';
|
||||
* $value = null;
|
||||
* $table->prepareValue($field, $value); // Doctrine_Null
|
||||
* </code>
|
||||
*
|
||||
* @throws Doctrine_Table_Exception if unserialization of array/object typed column fails or
|
||||
* @throws Doctrine_Table_Exception if uncompression of gzip typed column fails *
|
||||
* @param string $field the name of the field
|
||||
* @param string $value field value
|
||||
* @param string $typeHint A hint on the type of the value. If provided, the type lookup
|
||||
* for the field can be skipped. Used i.e. during hydration to
|
||||
* improve performance on large and/or complex results.
|
||||
* @return mixed prepared value
|
||||
* @todo To EntityManager. Make private and use in createEntity().
|
||||
* .. Or, maybe better: Move to hydrator for performance reasons.
|
||||
*/
|
||||
public function prepareValue($fieldName, $value, $typeHint = null)
|
||||
{
|
||||
if ($value === $this->_nullObject) {
|
||||
return $this->_nullObject;
|
||||
} else if ($value === null) {
|
||||
return null;
|
||||
} else {
|
||||
$type = is_null($typeHint) ? $this->_classMetadata->getTypeOf($fieldName) : $typeHint;
|
||||
switch ($type) {
|
||||
case 'integer':
|
||||
case 'string';
|
||||
// don't do any casting here PHP INT_MAX is smaller than what the databases support
|
||||
break;
|
||||
case 'enum':
|
||||
return $this->_classMetadata->enumValue($fieldName, $value);
|
||||
break;
|
||||
case 'boolean':
|
||||
return (boolean) $value;
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
if (is_string($value)) {
|
||||
$value = unserialize($value);
|
||||
if ($value === false) {
|
||||
throw new Doctrine_Mapper_Exception('Unserialization of ' . $fieldName . ' failed.');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
break;
|
||||
case 'gzip':
|
||||
$value = gzuncompress($value);
|
||||
if ($value === false) {
|
||||
throw new Doctrine_Mapper_Exception('Uncompressing of ' . $fieldName . ' failed.');
|
||||
}
|
||||
return $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* getComponentName
|
||||
*
|
||||
* @return void
|
||||
* @deprecated Use getMappedClassName()
|
||||
*/
|
||||
public function getComponentName()
|
||||
{
|
||||
return $this->_domainClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the class the mapper is used for.
|
||||
*/
|
||||
public function getMappedClassName()
|
||||
{
|
||||
return $this->_domainClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an entity and all it's related entities.
|
||||
*
|
||||
* @param Doctrine_Entity $record The entity to save.
|
||||
* @param Doctrine_Connection $conn The connection to use. Will default to the mapper's
|
||||
* connection.
|
||||
* @throws Doctrine_Mapper_Exception If the mapper is unable to save the given entity.
|
||||
*/
|
||||
public function save(Doctrine_Entity $record, Doctrine_Connection $conn = null)
|
||||
{
|
||||
if ( ! ($record instanceof $this->_domainClassName)) {
|
||||
throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . "
|
||||
can't save instances of type" . get_class($record) . ".");
|
||||
}
|
||||
|
||||
if ($conn === null) {
|
||||
$conn = $this->_conn;
|
||||
}
|
||||
|
||||
$state = $record->state();
|
||||
if ($state === Doctrine_Entity::STATE_LOCKED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$record->state(Doctrine_Entity::STATE_LOCKED);
|
||||
|
||||
try {
|
||||
$conn->beginInternalTransaction();
|
||||
$saveLater = $this->_saveRelated($record);
|
||||
|
||||
$record->state($state);
|
||||
|
||||
if ($record->isValid()) {
|
||||
$this->_insertOrUpdate($record);
|
||||
} else {
|
||||
$conn->transaction->addInvalid($record);
|
||||
}
|
||||
|
||||
$state = $record->state();
|
||||
$record->state(Doctrine_Entity::STATE_LOCKED);
|
||||
|
||||
foreach ($saveLater as $fk) {
|
||||
$alias = $fk->getAlias();
|
||||
if ($record->hasReference($alias)) {
|
||||
$obj = $record->$alias;
|
||||
// check that the related object is not an instance of Doctrine_Null
|
||||
if ( ! ($obj instanceof Doctrine_Null)) {
|
||||
$obj->save($conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save the MANY-TO-MANY associations
|
||||
$this->saveAssociations($record);
|
||||
// reset state
|
||||
$record->state($state);
|
||||
$conn->commit();
|
||||
} catch (Exception $e) {
|
||||
$conn->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates an entity, depending on it's state.
|
||||
*
|
||||
* @param Doctrine_Entity $record The entity to insert/update.
|
||||
*/
|
||||
protected function _insertOrUpdate(Doctrine_Entity $record)
|
||||
{
|
||||
$record->preSave();
|
||||
$this->notifyEntityListeners($record, 'preSave', Doctrine_Event::RECORD_SAVE);
|
||||
|
||||
switch ($record->state()) {
|
||||
case Doctrine_Entity::STATE_TDIRTY:
|
||||
$this->_insert($record);
|
||||
break;
|
||||
case Doctrine_Entity::STATE_DIRTY:
|
||||
case Doctrine_Entity::STATE_PROXY:
|
||||
$this->_update($record);
|
||||
break;
|
||||
case Doctrine_Entity::STATE_CLEAN:
|
||||
case Doctrine_Entity::STATE_TCLEAN:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
$record->postSave();
|
||||
$this->notifyEntityListeners($record, 'postSave', Doctrine_Event::RECORD_SAVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* saves the given record
|
||||
*
|
||||
* @param Doctrine_Entity $record
|
||||
* @return void
|
||||
*/
|
||||
public function saveSingleRecord(Doctrine_Entity $record)
|
||||
{
|
||||
$this->_insertOrUpdate($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* _saveRelated
|
||||
* saves all related records to $record
|
||||
*
|
||||
* @throws PDOException if something went wrong at database level
|
||||
* @param Doctrine_Entity $record
|
||||
*/
|
||||
protected function _saveRelated(Doctrine_Entity $record)
|
||||
{
|
||||
$saveLater = array();
|
||||
foreach ($record->getReferences() as $k => $v) {
|
||||
$rel = $record->getTable()->getRelation($k);
|
||||
|
||||
$local = $rel->getLocal();
|
||||
$foreign = $rel->getForeign();
|
||||
|
||||
if ($rel instanceof Doctrine_Relation_ForeignKey) {
|
||||
$saveLater[$k] = $rel;
|
||||
} else if ($rel instanceof Doctrine_Relation_LocalKey) {
|
||||
// ONE-TO-ONE relationship
|
||||
$obj = $record->get($rel->getAlias());
|
||||
|
||||
// Protection against infinite function recursion before attempting to save
|
||||
if ($obj instanceof Doctrine_Entity && $obj->isModified()) {
|
||||
$obj->save($this->_conn);
|
||||
|
||||
/** Can this be removed?
|
||||
$id = array_values($obj->identifier());
|
||||
|
||||
foreach ((array) $rel->getLocal() as $k => $field) {
|
||||
$record->set($field, $id[$k]);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $saveLater;
|
||||
}
|
||||
|
||||
/**
|
||||
* saveAssociations
|
||||
*
|
||||
* this method takes a diff of one-to-many / many-to-many original and
|
||||
* current collections and applies the changes
|
||||
*
|
||||
* for example if original many-to-many related collection has records with
|
||||
* primary keys 1,2 and 3 and the new collection has records with primary keys
|
||||
* 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then
|
||||
* save new associations to 4 and 5
|
||||
*
|
||||
* @throws Doctrine_Connection_Exception if something went wrong at database level
|
||||
* @param Doctrine_Entity $record
|
||||
* @return void
|
||||
*/
|
||||
public function saveAssociations(Doctrine_Entity $record)
|
||||
{
|
||||
foreach ($record->getReferences() as $relationName => $relatedObject) {
|
||||
if ($relatedObject === Doctrine_Null::$INSTANCE) {
|
||||
continue;
|
||||
}
|
||||
$rel = $record->getTable()->getRelation($relationName);
|
||||
|
||||
if ($rel instanceof Doctrine_Relation_Association) {
|
||||
$relatedObject->save($this->_conn);
|
||||
$assocTable = $rel->getAssociationTable();
|
||||
|
||||
foreach ($relatedObject->getDeleteDiff() as $r) {
|
||||
$query = 'DELETE FROM ' . $assocTable->getTableName()
|
||||
. ' WHERE ' . $rel->getForeign() . ' = ?'
|
||||
. ' AND ' . $rel->getLocal() . ' = ?';
|
||||
// FIXME: composite key support
|
||||
$ids1 = $r->identifier();
|
||||
$id1 = count($ids1) > 0 ? array_pop($ids1) : null;
|
||||
$ids2 = $record->identifier();
|
||||
$id2 = count($ids2) > 0 ? array_pop($ids2) : null;
|
||||
$this->_conn->execute($query, array($id1, $id2));
|
||||
}
|
||||
|
||||
$assocMapper = $this->_conn->getMapper($assocTable->getComponentName());
|
||||
foreach ($relatedObject->getInsertDiff() as $r) {
|
||||
$assocRecord = $assocMapper->create();
|
||||
$assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r);
|
||||
$assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record);
|
||||
$assocMapper->save($assocRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity.
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be updated
|
||||
* @return boolean whether or not the update was successful
|
||||
* @todo Move to Doctrine_Table (which will become Doctrine_Mapper).
|
||||
*/
|
||||
protected function _update(Doctrine_Entity $record)
|
||||
{
|
||||
$record->preUpdate();
|
||||
$this->notifyEntityListeners($record, 'preUpdate', Doctrine_Event::RECORD_UPDATE);
|
||||
|
||||
$table = $this->_classMetadata;
|
||||
$this->_doUpdate($record);
|
||||
|
||||
$record->postUpdate();
|
||||
$this->notifyEntityListeners($record, 'postUpdate', Doctrine_Event::RECORD_UPDATE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected function _doUpdate(Doctrine_Entity $entity);
|
||||
|
||||
/**
|
||||
* Inserts an entity.
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be inserted
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _insert(Doctrine_Entity $record)
|
||||
{
|
||||
$record->preInsert();
|
||||
$this->notifyEntityListeners($record, 'preInsert', Doctrine_Event::RECORD_INSERT);
|
||||
|
||||
$this->_doInsert($record);
|
||||
$this->addRecord($record);
|
||||
|
||||
$record->postInsert();
|
||||
$this->notifyEntityListeners($record, 'postInsert', Doctrine_Event::RECORD_INSERT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected function _doInsert(Doctrine_Entity $entity);
|
||||
|
||||
/**
|
||||
* Deletes given entity and all it's related entities.
|
||||
*
|
||||
* Triggered Events: onPreDelete, onDelete.
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
* @throws Doctrine_Mapper_Exception
|
||||
*/
|
||||
public function delete(Doctrine_Entity $record, Doctrine_Connection $conn = null)
|
||||
{
|
||||
if ( ! $record->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! ($record instanceof $this->_domainClassName)) {
|
||||
throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . "
|
||||
can't save instances of type" . get_class($record) . ".");
|
||||
}
|
||||
|
||||
if ($conn == null) {
|
||||
$conn = $this->_conn;
|
||||
}
|
||||
|
||||
$record->preDelete();
|
||||
$this->notifyEntityListeners($record, 'preDelete', Doctrine_Event::RECORD_DELETE);
|
||||
|
||||
$table = $this->_classMetadata;
|
||||
|
||||
$state = $record->state();
|
||||
$record->state(Doctrine_Entity::STATE_LOCKED);
|
||||
|
||||
$this->_doDelete($record);
|
||||
|
||||
$record->postDelete();
|
||||
$this->notifyEntityListeners($record, 'postDelete', Doctrine_Event::RECORD_DELETE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected function _doDelete(Doctrine_Entity $entity);
|
||||
|
||||
/**
|
||||
* Inserts a row into a table.
|
||||
*
|
||||
* @todo This method could be used to allow mapping to secondary table(s).
|
||||
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
|
||||
*/
|
||||
protected function _insertRow($tableName, array $data)
|
||||
{
|
||||
$this->_conn->insert($tableName, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes rows of a table.
|
||||
*
|
||||
* @todo This method could be used to allow mapping to secondary table(s).
|
||||
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
|
||||
*/
|
||||
protected function _deleteRow($tableName, array $identifierToMatch)
|
||||
{
|
||||
$this->_conn->delete($tableName, $identifierToMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes rows of a table.
|
||||
*
|
||||
* @todo This method could be used to allow mapping to secondary table(s).
|
||||
* @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable
|
||||
*/
|
||||
protected function _updateRow($tableName, array $data, array $identifierToMatch)
|
||||
{
|
||||
$this->_conn->update($tableName, $data, $identifierToMatch);
|
||||
}
|
||||
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->_classMetadata;
|
||||
}
|
||||
|
||||
public function getFieldName($columnName)
|
||||
{
|
||||
return $this->_classMetadata->getFieldName($columnName);
|
||||
}
|
||||
|
||||
public function getFieldNames()
|
||||
{
|
||||
if ($this->_fieldNames) {
|
||||
return $this->_fieldNames;
|
||||
}
|
||||
$this->_fieldNames = $this->_classMetadata->getFieldNames();
|
||||
return $this->_fieldNames;
|
||||
}
|
||||
|
||||
public function getOwningClass($fieldName)
|
||||
{
|
||||
return $this->_classMetadata;
|
||||
}
|
||||
|
||||
/* Hooks used during SQL query construction to manipulate the query. */
|
||||
|
||||
/**
|
||||
* Callback that is invoked during the SQL construction process.
|
||||
*/
|
||||
public function getCustomJoins()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is invoked during the SQL construction process.
|
||||
*/
|
||||
public function getCustomFields()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
6
lib/Doctrine/EntityPersister/Exception.php
Normal file
6
lib/Doctrine/EntityPersister/Exception.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
class Doctrine_EntityPersister_Exception extends Doctrine_Exception
|
||||
{
|
||||
|
||||
}
|
||||
?>
|
318
lib/Doctrine/EntityPersister/JoinedSubclass.php
Normal file
318
lib/Doctrine/EntityPersister/JoinedSubclass.php
Normal file
@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.phpdoctrine.org>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The joined mapping strategy maps a single entity instance to several tables in the
|
||||
* database as it is defined by <tt>Class Table Inheritance</tt>.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @package Doctrine
|
||||
* @subpackage JoinedSubclass
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @version $Revision$
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 2.0
|
||||
*/
|
||||
class Doctrine_EntityPersister_JoinedSubclass extends Doctrine_EntityPersister_Abstract
|
||||
{
|
||||
protected $_columnNameFieldNameMap = array();
|
||||
|
||||
/**
|
||||
* Inserts an entity that is part of a Class Table Inheritance hierarchy.
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be inserted
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _doInsert(Doctrine_Entity $record)
|
||||
{
|
||||
$class = $this->_classMetadata;
|
||||
$conn = $this->_conn;
|
||||
|
||||
$dataSet = $this->_groupFieldsByDefiningClass($record);
|
||||
$component = $class->getClassName();
|
||||
$classes = $class->getParentClasses();
|
||||
array_unshift($classes, $component);
|
||||
|
||||
try {
|
||||
$conn->beginInternalTransaction();
|
||||
$identifier = null;
|
||||
foreach (array_reverse($classes) as $k => $parent) {
|
||||
$parentClass = $conn->getClassMetadata($parent);
|
||||
if ($k == 0) {
|
||||
$identifierType = $parentClass->getIdentifierType();
|
||||
if ($identifierType == Doctrine::IDENTIFIER_AUTOINC) {
|
||||
$this->_insertRow($parentClass->getTableName(), $dataSet[$parent]);
|
||||
$identifier = $conn->sequence->lastInsertId();
|
||||
} else if ($identifierType == Doctrine::IDENTIFIER_SEQUENCE) {
|
||||
$seq = $record->getClassMetadata()->getTableOption('sequenceName');
|
||||
if ( ! empty($seq)) {
|
||||
$id = $conn->sequence->nextId($seq);
|
||||
$identifierFields = (array)$parentClass->getIdentifier();
|
||||
$dataSet[$parent][$identifierFields[0]] = $id;
|
||||
$this->_insertRow($parentClass->getTableName(), $dataSet[$parent]);
|
||||
}
|
||||
} else {
|
||||
throw new Doctrine_Mapper_Exception("Unsupported identifier type '$identifierType'.");
|
||||
}
|
||||
$record->assignIdentifier($identifier);
|
||||
} else {
|
||||
foreach ((array) $record->identifier() as $id => $value) {
|
||||
$dataSet[$parent][$parentClass->getColumnName($id)] = $value;
|
||||
}
|
||||
$this->_insertRow($parentClass->getTableName(), $dataSet[$parent]);
|
||||
}
|
||||
}
|
||||
$conn->commit();
|
||||
} catch (Exception $e) {
|
||||
$conn->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity that is part of a Class Table Inheritance hierarchy.
|
||||
*
|
||||
* @param Doctrine_Entity $record record to be updated
|
||||
* @return boolean whether or not the update was successful
|
||||
* @todo Move to Doctrine_Table (which will become Doctrine_Mapper).
|
||||
*/
|
||||
protected function _doUpdate(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
$classMetadata = $this->_classMetadata;
|
||||
$identifier = $this->_convertFieldToColumnNames($record->identifier(), $classMetadata);
|
||||
$dataSet = $this->_groupFieldsByDefiningClass($record);
|
||||
$component = $classMetadata->getClassName();
|
||||
$classes = $classMetadata->getParentClasses();
|
||||
array_unshift($classes, $component);
|
||||
|
||||
foreach ($record as $field => $value) {
|
||||
if ($value instanceof Doctrine_Entity) {
|
||||
if ( ! $value->exists()) {
|
||||
$value->save();
|
||||
}
|
||||
$idValues = $value->identifier();
|
||||
$record->set($field, $idValues[0]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_reverse($classes) as $class) {
|
||||
$parentTable = $conn->getClassMetadata($class);
|
||||
$this->_updateRow($parentTable->getTableName(), $dataSet[$class], $identifier);
|
||||
}
|
||||
|
||||
$record->assignIdentifier(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entity that is part of a Class Table Inheritance hierarchy.
|
||||
*
|
||||
*/
|
||||
protected function _doDelete(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
try {
|
||||
$class = $this->_classMetadata;
|
||||
$conn->beginInternalTransaction();
|
||||
$this->_deleteComposites($record);
|
||||
|
||||
$record->state(Doctrine_Entity::STATE_TDIRTY);
|
||||
|
||||
$identifier = $this->_convertFieldToColumnNames($record->identifier(), $class);
|
||||
|
||||
// run deletions, starting from the class, upwards the hierarchy
|
||||
$conn->delete($class->getTableName(), $identifier);
|
||||
foreach ($class->getParentClasses() as $parent) {
|
||||
$parentClass = $conn->getClassMetadata($parent);
|
||||
$this->_deleteRow($parentClass->getTableName(), $identifier);
|
||||
}
|
||||
|
||||
$record->state(Doctrine_Entity::STATE_TCLEAN);
|
||||
|
||||
$this->removeRecord($record); // @todo should be done in the unitofwork
|
||||
$conn->commit();
|
||||
} catch (Exception $e) {
|
||||
$conn->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all parent classes as INNER JOINs and subclasses as OUTER JOINs
|
||||
* to the query.
|
||||
*
|
||||
* Callback that is invoked during the SQL construction process.
|
||||
*
|
||||
* @return array The custom joins in the format <className> => <joinType>
|
||||
*/
|
||||
public function getCustomJoins()
|
||||
{
|
||||
$customJoins = array();
|
||||
$classMetadata = $this->_classMetadata;
|
||||
foreach ($classMetadata->getParentClasses() as $parentClass) {
|
||||
$customJoins[$parentClass] = 'INNER';
|
||||
}
|
||||
foreach ($classMetadata->getSubclasses() as $subClass) {
|
||||
if ($subClass != $this->getComponentName()) {
|
||||
$customJoins[$subClass] = 'LEFT';
|
||||
}
|
||||
}
|
||||
|
||||
return $customJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the discriminator column to the selected fields in a query as well as
|
||||
* all fields of subclasses. In Class Table Inheritance the default behavior is that
|
||||
* all subclasses are joined in through OUTER JOINs when querying a base class.
|
||||
*
|
||||
* Callback that is invoked during the SQL construction process.
|
||||
*
|
||||
* @return array An array with the field names that will get added to the query.
|
||||
*/
|
||||
public function getCustomFields()
|
||||
{
|
||||
$classMetadata = $this->_classMetadata;
|
||||
$conn = $this->_conn;
|
||||
$fields = array($classMetadata->getInheritanceOption('discriminatorColumn'));
|
||||
if ($classMetadata->getSubclasses()) {
|
||||
foreach ($classMetadata->getSubclasses() as $subClass) {
|
||||
$fields = array_merge($conn->getClassMetadata($subClass)->getFieldNames(), $fields);
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getFieldNames()
|
||||
{
|
||||
if ($this->_fieldNames) {
|
||||
return $this->_fieldNames;
|
||||
}
|
||||
|
||||
$fieldNames = $this->_classMetadata->getFieldNames();
|
||||
$this->_fieldNames = array_unique($fieldNames);
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getFieldName($columnName)
|
||||
{
|
||||
if (isset($this->_columnNameFieldNameMap[$columnName])) {
|
||||
return $this->_columnNameFieldNameMap[$columnName];
|
||||
}
|
||||
|
||||
$classMetadata = $this->_classMetadata;
|
||||
$conn = $this->_conn;
|
||||
|
||||
if ($classMetadata->hasColumn($columnName)) {
|
||||
$this->_columnNameFieldNameMap[$columnName] = $classMetadata->getFieldName($columnName);
|
||||
return $this->_columnNameFieldNameMap[$columnName];
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getSubclasses() as $subClass) {
|
||||
$subTable = $conn->getClassMetadata($subClass);
|
||||
if ($subTable->hasColumn($columnName)) {
|
||||
$this->_columnNameFieldNameMap[$columnName] = $subTable->getFieldName($columnName);
|
||||
return $this->_columnNameFieldNameMap[$columnName];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Doctrine_Mapper_Exception("No field name found for column name '$columnName'.");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @todo Looks like this better belongs into the ClassMetadata class.
|
||||
*/
|
||||
public function getOwningClass($fieldName)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
$classMetadata = $this->_classMetadata;
|
||||
if ($classMetadata->hasField($fieldName) && ! $classMetadata->isInheritedField($fieldName)) {
|
||||
return $classMetadata;
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getParentClasses() as $parentClass) {
|
||||
$parentTable = $conn->getClassMetadata($parentClass);
|
||||
if ($parentTable->hasField($fieldName) && ! $parentTable->isInheritedField($fieldName)) {
|
||||
return $parentTable;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$classMetadata->getSubclasses() as $subClass) {
|
||||
$subTable = $conn->getClassMetadata($subClass);
|
||||
if ($subTable->hasField($fieldName) && ! $subTable->isInheritedField($fieldName)) {
|
||||
return $subTable;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Doctrine_Mapper_Exception("Unable to find defining class of field '$fieldName'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the fields of the entity and creates a map in which the field names
|
||||
* are grouped by the class names they belong to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _groupFieldsByDefiningClass(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
$classMetadata = $this->_classMetadata;
|
||||
$dataSet = array();
|
||||
$component = $classMetadata->getClassName();
|
||||
$array = $record->getPrepared();
|
||||
|
||||
$classes = array_merge(array($component), $classMetadata->getParentClasses());
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$dataSet[$class] = array();
|
||||
$parentClassMetadata = $conn->getClassMetadata($class);
|
||||
foreach ($parentClassMetadata->getColumns() as $columnName => $definition) {
|
||||
if ((isset($definition['primary']) && $definition['primary'] === true) ||
|
||||
(isset($definition['inherited']) && $definition['inherited'] === true)) {
|
||||
continue;
|
||||
}
|
||||
$fieldName = $classMetadata->getFieldName($columnName);
|
||||
if ( ! array_key_exists($fieldName, $array)) {
|
||||
continue;
|
||||
}
|
||||
$dataSet[$class][$columnName] = $array[$fieldName];
|
||||
}
|
||||
}
|
||||
|
||||
return $dataSet;
|
||||
}
|
||||
}
|
||||
|
120
lib/Doctrine/EntityPersister/Standard.php
Normal file
120
lib/Doctrine/EntityPersister/Standard.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.phpdoctrine.org>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The default mapping strategy maps a single entity instance to a single database table,
|
||||
* as is the case in Single Table Inheritance & Concrete Table Inheritance.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @package Doctrine
|
||||
* @subpackage Abstract
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @version $Revision$
|
||||
* @link www.phpdoctrine.org
|
||||
* @since 2.0
|
||||
*/
|
||||
class Doctrine_EntityPersister_Standard extends Doctrine_EntityPersister_Abstract
|
||||
{
|
||||
/**
|
||||
* Deletes an entity.
|
||||
*/
|
||||
protected function _doDelete(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
$metadata = $this->_classMetadata;
|
||||
try {
|
||||
$conn->beginInternalTransaction();
|
||||
$this->_deleteComposites($record);
|
||||
|
||||
$record->state(Doctrine_Entity::STATE_TDIRTY);
|
||||
|
||||
$identifier = $this->_convertFieldToColumnNames($record->identifier(), $metadata);
|
||||
$this->_deleteRow($metadata->getTableName(), $identifier);
|
||||
$record->state(Doctrine_Entity::STATE_TCLEAN);
|
||||
|
||||
$this->removeRecord($record);
|
||||
$conn->commit();
|
||||
} catch (Exception $e) {
|
||||
$conn->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a single entity into the database, without any related entities.
|
||||
*
|
||||
* @param Doctrine_Entity $record The entity to insert.
|
||||
*/
|
||||
protected function _doInsert(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
|
||||
$fields = $record->getPrepared();
|
||||
if (empty($fields)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//$class = $record->getClassMetadata();
|
||||
$class = $this->_classMetadata;
|
||||
$identifier = $class->getIdentifier();
|
||||
$fields = $this->_convertFieldToColumnNames($fields, $class);
|
||||
|
||||
$seq = $class->getTableOption('sequenceName');
|
||||
if ( ! empty($seq)) {
|
||||
$id = $conn->sequence->nextId($seq);
|
||||
$seqName = $identifier[0];
|
||||
$fields[$seqName] = $id;
|
||||
$record->assignIdentifier($id);
|
||||
}
|
||||
|
||||
$this->_insertRow($class->getTableName(), $fields);
|
||||
|
||||
if (empty($seq) && count($identifier) == 1 &&
|
||||
$class->getIdentifierType() != Doctrine::IDENTIFIER_NATURAL) {
|
||||
if (strtolower($conn->getName()) == 'pgsql') {
|
||||
$seq = $class->getTableName() . '_' . $identifier[0];
|
||||
}
|
||||
|
||||
$id = $conn->sequence->lastInsertId($seq);
|
||||
|
||||
if ( ! $id) {
|
||||
throw new Doctrine_Mapper_Exception("Couldn't get last insert identifier.");
|
||||
}
|
||||
|
||||
$record->assignIdentifier($id);
|
||||
} else {
|
||||
$record->assignIdentifier(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity.
|
||||
*/
|
||||
protected function _doUpdate(Doctrine_Entity $record)
|
||||
{
|
||||
$conn = $this->_conn;
|
||||
$classMetadata = $this->_classMetadata;
|
||||
$identifier = $this->_convertFieldToColumnNames($record->identifier(), $classMetadata);
|
||||
$data = $this->_convertFieldToColumnNames($record->getPrepared(), $classMetadata);
|
||||
$this->_updateRow($classMetadata->getTableName(), $data, $identifier);
|
||||
$record->assignIdentifier(true);
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@
|
||||
* and turn them into useable structures.
|
||||
*
|
||||
* Runtime complexity: The following gives the overall number of iterations
|
||||
* required to process a result set.
|
||||
* required to process a result set when using identity hydration
|
||||
* (HYDRATE_IDENTITY_OBJECT or HYDRATE_IDENTITY_ARRAY).
|
||||
*
|
||||
* <code>numRowsInResult * numColumnsInResult + numRowsInResult * numClassesInQuery</code>
|
||||
*
|
||||
@ -32,15 +33,19 @@
|
||||
*
|
||||
* <code>(numRowsInResult * (numColumnsInResult + numClassesInQuery))</code>
|
||||
*
|
||||
* Note that this is only a crude definition of the complexity as it also heavily
|
||||
* For scalar hydration (HYDRATE_SCALAR) it's:
|
||||
*
|
||||
* <code>numRowsInResult * numColumnsInResult</code>
|
||||
*
|
||||
* Note that this is only a crude definition as it also heavily
|
||||
* depends on the complexity of all the single operations that are performed in
|
||||
* each iteration.
|
||||
*
|
||||
* As can be seen, the number of columns in the result has the most impact on
|
||||
* the overall performance (apart from the row counr, of course), since numClassesInQuery
|
||||
* the overall performance (apart from the row count, of course), since numClassesInQuery
|
||||
* is usually pretty low.
|
||||
* That's why the performance of the gatherRowData() method which is responsible
|
||||
* for the "numRowsInResult * numColumnsInResult" part is crucial to fast hydraton.
|
||||
* That's why the performance of the _gatherRowData() methods which are responsible
|
||||
* for the "numRowsInResult * numColumnsInResult" part is crucial to fast hydration.
|
||||
*
|
||||
* @package Doctrine
|
||||
* @subpackage Hydrator
|
||||
@ -54,14 +59,10 @@
|
||||
class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
{
|
||||
/**
|
||||
* hydrateResultSet
|
||||
* parses the data returned by statement object
|
||||
* Parses the data returned by statement object.
|
||||
*
|
||||
* This is method defines the core of Doctrine's object population algorithm.
|
||||
*
|
||||
* The key idea is the loop over the rowset only once doing all the needed operations
|
||||
* within this massive loop.
|
||||
*
|
||||
* @todo: Detailed documentation. Refactor (too long & nesting level).
|
||||
*
|
||||
* @param mixed $stmt
|
||||
@ -125,7 +126,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
$idTemplate = array();
|
||||
|
||||
// Holds the resulting hydrated data structure
|
||||
if ($parserResult->isMixedQuery()) {
|
||||
if ($parserResult->isMixedQuery() || $hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
$result = array();
|
||||
} else {
|
||||
$result = $driver->getElementCollection($rootComponentName);
|
||||
@ -140,7 +141,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
// disable lazy-loading of related elements during hydration
|
||||
$component['table']->setAttribute(Doctrine::ATTR_LOAD_REFERENCES, false);
|
||||
$componentName = $component['table']->getClassName();
|
||||
$listeners[$componentName] = $component['table']->getRecordListener();
|
||||
//$listeners[$componentName] = $component['table']->getRecordListener();
|
||||
$identifierMap[$dqlAlias] = array();
|
||||
$resultPointers[$dqlAlias] = array();
|
||||
$idTemplate[$dqlAlias] = '';
|
||||
@ -149,6 +150,11 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
// Process result set
|
||||
$cache = array();
|
||||
while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
|
||||
if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
$result[] = $this->_gatherScalarRowData($data, $cache);
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $idTemplate; // initialize the id-memory
|
||||
$nonemptyComponents = array();
|
||||
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
|
||||
@ -160,8 +166,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
$componentName = $class->getComponentName();
|
||||
|
||||
// just event stuff
|
||||
$event->set('data', $rowData[$rootAlias]);
|
||||
$listeners[$componentName]->preHydrate($event);
|
||||
//$event->set('data', $rowData[$rootAlias]);
|
||||
//$listeners[$componentName]->preHydrate($event);
|
||||
//--
|
||||
|
||||
// Check for an existing element
|
||||
@ -170,8 +176,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
$element = $driver->getElement($rowData[$rootAlias], $componentName);
|
||||
|
||||
// just event stuff
|
||||
$event->set('data', $element);
|
||||
$listeners[$componentName]->postHydrate($event);
|
||||
//$event->set('data', $element);
|
||||
//$listeners[$componentName]->postHydrate($event);
|
||||
//--
|
||||
|
||||
// do we need to index by a custom field?
|
||||
@ -201,7 +207,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
} else {
|
||||
$index = $identifierMap[$rootAlias][$id[$rootAlias]];
|
||||
}
|
||||
|
||||
$this->_setLastElement($resultPointers, $result, $index, $rootAlias, false);
|
||||
unset($rowData[$rootAlias]);
|
||||
// end hydrate data of the root component for the current row
|
||||
@ -212,7 +217,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
unset($rowData['scalars']);
|
||||
}
|
||||
|
||||
// $resultPointers[$rootAlias] now points to the last element in $result.
|
||||
// now hydrate the rest of the data found in the current row, that belongs to other
|
||||
// (related) components.
|
||||
foreach ($rowData as $dqlAlias => $data) {
|
||||
@ -221,14 +225,13 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
$componentName = $map['table']->getComponentName();
|
||||
|
||||
// just event stuff
|
||||
$event->set('data', $data);
|
||||
$listeners[$componentName]->preHydrate($event);
|
||||
//$event->set('data', $data);
|
||||
//$listeners[$componentName]->preHydrate($event);
|
||||
//--
|
||||
|
||||
$parent = $map['parent'];
|
||||
$relation = $map['relation'];
|
||||
$relationAlias = $relation->getAlias();
|
||||
|
||||
$path = $parent . '.' . $dqlAlias;
|
||||
|
||||
// pick the right element that will get the associated element attached
|
||||
@ -252,8 +255,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
$element = $driver->getElement($data, $componentName);
|
||||
|
||||
// just event stuff
|
||||
$event->set('data', $element);
|
||||
$listeners[$componentName]->postHydrate($event);
|
||||
//$event->set('data', $element);
|
||||
//$listeners[$componentName]->postHydrate($event);
|
||||
//--
|
||||
|
||||
if ($field = $this->_getCustomIndexField($dqlAlias)) {
|
||||
@ -268,7 +271,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
} else {
|
||||
$driver->addRelatedElement($baseElement, $relationAlias, $element);
|
||||
}
|
||||
|
||||
$identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey(
|
||||
$driver->getReferenceValue($baseElement, $relationAlias));
|
||||
} else {
|
||||
@ -361,9 +363,12 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the fields of a data row into a new array, grouped by the component
|
||||
* Processes a row of the result set.
|
||||
* Used for identity hydration (HYDRATE_IDENTITY_OBJECT and HYDRATE_IDENTITY_ARRAY).
|
||||
* Puts the elements of a result row into a new array, grouped by the class
|
||||
* they belong to. The column names in the result set are mapped to their
|
||||
* field names during this procedure.
|
||||
* field names during this procedure as well as any necessary conversions on
|
||||
* the values applied.
|
||||
*
|
||||
* @return array An array with all the fields (name => value) of the data row,
|
||||
* grouped by their component (alias).
|
||||
@ -377,7 +382,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
if ( ! isset($cache[$key])) {
|
||||
// check ignored names. fastest solution for now. if we get more we'll start
|
||||
// to introduce a list.
|
||||
if ($key == 'doctrine_rownum') continue;
|
||||
if ($this->_isIgnoredName($key)) continue;
|
||||
|
||||
// cache general information like the column name <-> field name mapping
|
||||
$e = explode('__', $key);
|
||||
@ -441,18 +446,94 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
|
||||
return $rowData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a row of the result set.
|
||||
* Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
|
||||
* simply converts column names to field names and properly prepares the
|
||||
* values. The resulting row has the same number of elements as before.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $cache
|
||||
* @return array The processed row.
|
||||
*/
|
||||
private function _gatherScalarRowData(&$data, &$cache)
|
||||
{
|
||||
$rowData = array();
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
// Parse each column name only once. Cache the results.
|
||||
if ( ! isset($cache[$key])) {
|
||||
// check ignored names. fastest solution for now. if we get more we'll start
|
||||
// to introduce a list.
|
||||
if ($this->_isIgnoredName($key)) continue;
|
||||
|
||||
// cache general information like the column name <-> field name mapping
|
||||
$e = explode('__', $key);
|
||||
$columnName = strtolower(array_pop($e));
|
||||
$cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))];
|
||||
$mapper = $this->_queryComponents[$cache[$key]['dqlAlias']]['mapper'];
|
||||
$classMetadata = $mapper->getClassMetadata();
|
||||
// check whether it's an aggregate value or a regular field
|
||||
if (isset($this->_queryComponents[$cache[$key]['dqlAlias']]['agg'][$columnName])) {
|
||||
$fieldName = $this->_queryComponents[$cache[$key]['dqlAlias']]['agg'][$columnName];
|
||||
$cache[$key]['isScalar'] = true;
|
||||
} else {
|
||||
$fieldName = $mapper->getFieldName($columnName);
|
||||
$cache[$key]['isScalar'] = false;
|
||||
}
|
||||
|
||||
$cache[$key]['fieldName'] = $fieldName;
|
||||
|
||||
// cache type information
|
||||
$type = $classMetadata->getTypeOfColumn($columnName);
|
||||
if ($type == 'integer' || $type == 'string') {
|
||||
$cache[$key]['isSimpleType'] = true;
|
||||
} else {
|
||||
$cache[$key]['type'] = $type;
|
||||
$cache[$key]['isSimpleType'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$mapper = $this->_queryComponents[$cache[$key]['dqlAlias']]['mapper'];
|
||||
$dqlAlias = $cache[$key]['dqlAlias'];
|
||||
$fieldName = $cache[$key]['fieldName'];
|
||||
|
||||
if ($cache[$key]['isSimpleType'] || $cache[$key]['isScalar']) {
|
||||
$rowData[$dqlAlias . '_' . $fieldName] = $value;
|
||||
} else {
|
||||
$rowData[$dqlAlias . '_' . $fieldName] = $mapper->prepareValue(
|
||||
$fieldName, $value, $cache[$key]['type']);
|
||||
}
|
||||
}
|
||||
|
||||
return $rowData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom field used for indexing for the specified component alias.
|
||||
*
|
||||
* @return string The field name of the field used for indexing or NULL
|
||||
* if the component does not use any custom field indices.
|
||||
*/
|
||||
protected function _getCustomIndexField($alias)
|
||||
private function _getCustomIndexField($alias)
|
||||
{
|
||||
return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a name is ignored. Used during result set parsing to skip
|
||||
* certain elements in the result set that do not have any meaning for the result.
|
||||
* (I.e. ORACLE limit/offset emulation adds doctrine_rownum to the result set).
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
private function _isIgnoredName($name)
|
||||
{
|
||||
return $name == 'doctrine_rownum';
|
||||
}
|
||||
|
||||
/** Needed only temporarily until the new parser is ready */
|
||||
private $_isResultMixed = false;
|
||||
public function setResultMixed($bool)
|
||||
{
|
||||
|
@ -17,7 +17,8 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
{
|
||||
return array(
|
||||
array('hydrationMode' => Doctrine::HYDRATE_RECORD),
|
||||
array('hydrationMode' => Doctrine::HYDRATE_ARRAY)
|
||||
array('hydrationMode' => Doctrine::HYDRATE_ARRAY),
|
||||
array('hydrationMode' => Doctrine::HYDRATE_SCALAR)
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,18 +77,27 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
$result = $hydrator->hydrateResultSet($this->_createParserResult(
|
||||
$stmt, $queryComponents, $tableAliasMap, $hydrationMode));
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals(1, $result[0]['id']);
|
||||
$this->assertEquals('romanb', $result[0]['name']);
|
||||
$this->assertEquals(2, $result[1]['id']);
|
||||
$this->assertEquals('jwage', $result[1]['name']);
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals(1, $result[0]['id']);
|
||||
$this->assertEquals('romanb', $result[0]['name']);
|
||||
$this->assertEquals(2, $result[1]['id']);
|
||||
$this->assertEquals('jwage', $result[1]['name']);
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result instanceof Doctrine_Collection);
|
||||
$this->assertTrue($result[0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1] instanceof Doctrine_Entity);
|
||||
} else {
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_ARRAY) {
|
||||
$this->assertTrue(is_array($result));
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertEquals('romanb', $result[0]['u_name']);
|
||||
$this->assertEquals(1, $result[0]['u_id']);
|
||||
$this->assertEquals('jwage', $result[1]['u_name']);
|
||||
$this->assertEquals(2, $result[1]['u_id']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,21 +170,23 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
//var_dump($result);
|
||||
}
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
|
||||
// first user => 2 phonenumbers
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
// first user => 2 phonenumbers
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result[0][0] instanceof Doctrine_Entity);
|
||||
@ -183,6 +195,16 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
$this->assertTrue($result[0][0]['phonenumbers'][1] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1][0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1][0]['phonenumbers'] instanceof Doctrine_Collection);
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertEquals(3, count($result));
|
||||
|
||||
$this->assertEquals(1, $result[0]['u_id']);
|
||||
$this->assertEquals('developer', $result[0]['u_status']);
|
||||
$this->assertEquals('ROMANB', $result[0]['u_nameUpper']);
|
||||
$this->assertEquals(42, $result[0]['p_phonenumber']);
|
||||
|
||||
// ... more checks to come
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +259,6 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$stmt = new Doctrine_HydratorMockStatement($resultSet);
|
||||
$hydrator = new Doctrine_HydratorNew($this->_em);
|
||||
|
||||
@ -245,19 +266,31 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
$stmt, $queryComponents, $tableAliasMap, $hydrationMode, true));
|
||||
//var_dump($result);
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
|
||||
// first user => 2 phonenumbers
|
||||
$this->assertEquals(2, $result[0]['numPhones']);
|
||||
// second user => 1 phonenumber
|
||||
$this->assertEquals(1, $result[1]['numPhones']);
|
||||
// first user => 2 phonenumbers
|
||||
$this->assertEquals(2, $result[0]['numPhones']);
|
||||
// second user => 1 phonenumber
|
||||
$this->assertEquals(1, $result[1]['numPhones']);
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result[0][0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1][0] instanceof Doctrine_Entity);
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
$this->assertEquals(2, count($result));
|
||||
|
||||
$this->assertEquals(1, $result[0]['u_id']);
|
||||
$this->assertEquals('developer', $result[0]['u_status']);
|
||||
$this->assertEquals(2, $result[0]['p_numPhones']);
|
||||
|
||||
$this->assertEquals(2, $result[1]['u_id']);
|
||||
$this->assertEquals('developer', $result[1]['u_status']);
|
||||
$this->assertEquals(1, $result[1]['p_numPhones']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,31 +363,35 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
//var_dump($result);
|
||||
}
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
|
||||
// first user => 2 phonenumbers. notice the custom indexing by user id
|
||||
$this->assertEquals(2, count($result[0]['1']['phonenumbers']));
|
||||
// second user => 1 phonenumber. notice the custom indexing by user id
|
||||
$this->assertEquals(1, count($result[1]['2']['phonenumbers']));
|
||||
|
||||
// first user => 2 phonenumbers. notice the custom indexing by user id
|
||||
$this->assertEquals(2, count($result[0]['1']['phonenumbers']));
|
||||
// second user => 1 phonenumber. notice the custom indexing by user id
|
||||
$this->assertEquals(1, count($result[1]['2']['phonenumbers']));
|
||||
// test the custom indexing of the phonenumbers
|
||||
$this->assertTrue(isset($result[0]['1']['phonenumbers']['42']));
|
||||
$this->assertTrue(isset($result[0]['1']['phonenumbers']['43']));
|
||||
$this->assertTrue(isset($result[1]['2']['phonenumbers']['91']));
|
||||
|
||||
// test the custom indexing of the phonenumbers
|
||||
$this->assertTrue(isset($result[0]['1']['phonenumbers']['42']));
|
||||
$this->assertTrue(isset($result[0]['1']['phonenumbers']['43']));
|
||||
$this->assertTrue(isset($result[1]['2']['phonenumbers']['91']));
|
||||
|
||||
// test the scalar values
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
// test the scalar values
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result[0]['1'] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1]['2'] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[0]['1']['phonenumbers'] instanceof Doctrine_Collection);
|
||||
$this->assertEquals(2, count($result[0]['1']['phonenumbers']));
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
// NOTE: Indexing has no effect with HYDRATE_SCALAR
|
||||
//... asserts to come
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,28 +506,30 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
//var_dump($result);
|
||||
}
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
|
||||
// first user => 2 phonenumbers, 2 articles
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[0][0]['articles']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber, 2 articles
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[1][0]['articles']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
// first user => 2 phonenumbers, 2 articles
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[0][0]['articles']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber, 2 articles
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[1][0]['articles']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
|
||||
$this->assertEquals('Getting things done!', $result[0][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('Getting things done!', $result[0][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']);
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result[0][0] instanceof Doctrine_Entity);
|
||||
@ -505,6 +544,10 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
$this->assertTrue($result[1][0]['phonenumbers'][0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1][0]['articles'][0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1][0]['articles'][1] instanceof Doctrine_Entity);
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
//...
|
||||
$this->assertEquals(6, count($result));
|
||||
//var_dump($result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,36 +685,38 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
//var_dump($result);
|
||||
}
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
|
||||
// first user => 2 phonenumbers, 2 articles, 1 comment on first article
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[0][0]['articles']));
|
||||
$this->assertEquals(1, count($result[0][0]['articles'][0]['comments']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber, 2 articles, no comments
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[1][0]['articles']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
// first user => 2 phonenumbers, 2 articles, 1 comment on first article
|
||||
$this->assertEquals(2, count($result[0][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[0][0]['articles']));
|
||||
$this->assertEquals(1, count($result[0][0]['articles'][0]['comments']));
|
||||
$this->assertEquals('ROMANB', $result[0]['nameUpper']);
|
||||
// second user => 1 phonenumber, 2 articles, no comments
|
||||
$this->assertEquals(1, count($result[1][0]['phonenumbers']));
|
||||
$this->assertEquals(2, count($result[1][0]['articles']));
|
||||
$this->assertEquals('JWAGE', $result[1]['nameUpper']);
|
||||
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']);
|
||||
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']);
|
||||
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']);
|
||||
|
||||
$this->assertEquals('Getting things done!', $result[0][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('Getting things done!', $result[0][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']);
|
||||
$this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']);
|
||||
$this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']);
|
||||
|
||||
$this->assertEquals('First!', $result[0][0]['articles'][0]['comments'][0]['topic']);
|
||||
$this->assertEquals('First!', $result[0][0]['articles'][0]['comments'][0]['topic']);
|
||||
|
||||
$this->assertTrue(isset($result[0][0]['articles'][0]['comments']));
|
||||
$this->assertFalse(isset($result[0][0]['articles'][1]['comments']));
|
||||
$this->assertFalse(isset($result[1][0]['articles'][0]['comments']));
|
||||
$this->assertFalse(isset($result[1][0]['articles'][1]['comments']));
|
||||
$this->assertTrue(isset($result[0][0]['articles'][0]['comments']));
|
||||
$this->assertFalse(isset($result[0][0]['articles'][1]['comments']));
|
||||
$this->assertFalse(isset($result[1][0]['articles'][0]['comments']));
|
||||
$this->assertFalse(isset($result[1][0]['articles'][1]['comments']));
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result[0][0] instanceof Doctrine_Entity);
|
||||
@ -691,6 +736,8 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
// article comments
|
||||
$this->assertTrue($result[0][0]['articles'][0]['comments'] instanceof Doctrine_Collection);
|
||||
$this->assertTrue($result[0][0]['articles'][0]['comments'][0] instanceof Doctrine_Entity);
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
//...
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,21 +834,28 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase
|
||||
//var_dump($result);
|
||||
}
|
||||
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(isset($result[0]['boards']));
|
||||
$this->assertEquals(3, count($result[0]['boards']));
|
||||
$this->assertTrue(isset($result[1]['boards']));
|
||||
$this->assertEquals(1, count($result[1]['boards']));
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY || $hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertEquals(2, count($result));
|
||||
$this->assertTrue(isset($result[0]['boards']));
|
||||
$this->assertEquals(3, count($result[0]['boards']));
|
||||
$this->assertTrue(isset($result[1]['boards']));
|
||||
$this->assertEquals(1, count($result[1]['boards']));
|
||||
}
|
||||
|
||||
if ($hydrationMode == Doctrine::HYDRATE_ARRAY) {
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertTrue(is_array($result[0]));
|
||||
$this->assertTrue(is_array($result[1]));
|
||||
} else {
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_RECORD) {
|
||||
$this->assertTrue($result instanceof Doctrine_Collection);
|
||||
$this->assertTrue($result[0] instanceof Doctrine_Entity);
|
||||
$this->assertTrue($result[1] instanceof Doctrine_Entity);
|
||||
} else if ($hydrationMode == Doctrine::HYDRATE_SCALAR) {
|
||||
//...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user