Commit d9975c36 authored by romanb's avatar romanb

Checkin of occasional work from the past weeks.

parent c43f9588
<?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>.
*/
#namespace Doctrine::ORM::Mapping;
/**
* Base class for association mappings.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @todo Rename to AssociationMapping.
*/
class Doctrine_Association implements Serializable
{
const FETCH_MANUAL = 1;
const FETCH_LAZY = 2;
const FETCH_EAGER = 3;
/**
* Cascade types enumeration.
*
* @var array
*/
protected static $_cascadeTypes = array(
'all',
'none',
'save',
'delete',
'refresh'
);
protected $_cascades = array();
protected $_isCascadeDelete;
protected $_isCascadeSave;
protected $_isCascadeRefresh;
/**
* The fetch mode used for the association.
*
* @var integer
*/
protected $_fetchMode = self::FETCH_MANUAL;
/**
* Flag that indicates whether the class that defines this mapping is
* the owning side of the association.
*
* @var boolean
*/
protected $_isOwningSide = true;
/**
* The name of the source Entity (the Entity that defines this mapping).
*
* @var string
*/
protected $_sourceEntityName;
/**
* The name of the target Entity (the Enitity that is the target of the
* association).
*
* @var unknown_type
*/
protected $_targetEntityName;
/**
* Identifies the field on the source class (the class this AssociationMapping
* belongs to) that represents the association.
*
* @var string
*/
protected $_sourceFieldName;
/**
* Identifies the field on the owning side that has the mapping for the
* association.
*
* @var string
*/
protected $_mappedByFieldName;
/**
* Constructor.
* Creates a new AssociationMapping.
*
* @param array $mapping The mapping definition.
*/
public function __construct(array $mapping)
{
$this->_validateMapping($mapping);
if ($this->_isOwningSide) {
$this->_sourceEntityName = $mapping['sourceEntity'];
$this->_targetEntityName = $mapping['targetEntity'];
$this->_sourceFieldName = $mapping['fieldName'];
} else {
$this->_mappedByFieldName = $mapping['mappedBy'];
}
}
/**
* Validates & completes the mapping. Mapping defaults are applied here.
*
* @param array $mapping
* @return array The validated & completed mapping.
*/
protected function _validateMapping(array $mapping)
{
if (isset($mapping['mappedBy'])) {
$this->_isOwningSide = false;
}
if ($this->_isOwningSide) {
if ( ! isset($mapping['targetEntity'])) {
throw Doctrine_MappingException::missingTargetEntity();
} else if ( ! isset($mapping['fieldName'])) {
throw Doctrine_MappingException::missingFieldName();
}
}
return $mapping;
}
public function isCascadeDelete()
{
if (is_null($this->_isCascadeDelete)) {
$this->_isCascadeDelete = in_array('delete', $this->_cascades);
}
return $this->_isCascadeDelete;
}
public function isCascadeSave()
{
if (is_null($this->_isCascadeSave)) {
$this->_isCascadeSave = in_array('save', $this->_cascades);
}
return $this->_isCascadeSave;
}
public function isCascadeRefresh()
{
if (is_null($this->_isCascadeRefresh)) {
$this->_isCascadeRefresh = in_array('refresh', $this->_cascades);
}
return $this->_isCascadeRefresh;
}
public function isEagerlyFetched()
{
return $this->_fetchMode == self::FETCH_EAGER;
}
public function isLazilyFetched()
{
return $this->_fetchMode == self::FETCH_LAZY;
}
public function isManuallyFetched()
{
return $this->_fetchMode == self::FETCH_MANUAL;
}
public function isOwningSide()
{
return $this->_isOwningSide;
}
public function isInverseSide()
{
return ! $this->_isOwningSide;
}
public function getSourceEntityName()
{
return $this->_sourceEntityName;
}
public function getTargetEntityName()
{
return $this->_targetEntityName;
}
/**
* Get the name of the field the association is mapped into.
*
*/
public function getSourceFieldName()
{
return $this->_sourceFieldName;
}
public function getMappedByFieldName()
{
return $this->_mappedByFieldName;
}
/* Serializable implementation */
public function serialize()
{
return "";
}
public function unserialize($serialized)
{
return true;
}
}
?>
\ No newline at end of file
<?php
#namespace Doctrine::ORM::Mappings;
/**
* A many-to-many mapping describes the mapping between two collections of
* entities.
*
* @since 2.0
* @todo Rename to ManyToManyMapping
*/
class Doctrine_Association_ManyToMany extends Doctrine_Association
{
/**
* Whether the mapping uses an association class.
*
* @var boolean
*/
private $_usesAssociationClass;
/**
* The name of the association class (if an association class is used).
*
* @var string
*/
private $_associationClassName;
/**
* The name of the intermediate table.
*
* @var string
*/
private $_relationTableName;
/** The field in the source table that corresponds to the key in the relation table */
protected $_sourceKeyFields;
/** The field in the target table that corresponds to the key in the relation table */
protected $_targetKeyFields;
/** The field in the intermediate table that corresponds to the key in the source table */
protected $_sourceRelationKeyFields;
/** The field in the intermediate table that corresponds to the key in the target table */
protected $_targetRelationKeyFields;
/**
* Whether the mapping uses an association class for the intermediary
* table.
*
* @return boolean
*/
public function usesAssociationClass()
{
}
/**
* Gets the name of the intermediate table.
*
* @return string
*/
public function getRelationTableName()
{
return $this->_relationTableName;
}
/**
* Gets the name of the association class.
*
* @return string
*/
public function getAssociationClassName()
{
return $this->_associationClassName;
}
}
?>
\ No newline at end of file
<?php
#namespace Doctrine::ORM::Mappings;
class Doctrine_Association_OneToMany extends Doctrine_Association
{
/** The target foreign key fields that reference the sourceKeyFields. */
protected $_targetForeignKeyFields;
/** The (typically primary) source key fields that are referenced by the targetForeignKeyFields. */
protected $_sourceKeyFields;
/** This maps the target foreign key fields to the corresponding (primary) source key fields. */
protected $_targetForeignKeysToSourceKeys;
/** This maps the (primary) source key fields to the corresponding target foreign key fields. */
protected $_sourceKeysToTargetForeignKeys;
/** Whether to delete orphaned elements (removed from the collection) */
protected $_isCascadeDeleteOrphan = false;
public function isCascadeDeleteOrphan()
{
return $this->_isCascadeDeleteOrphan;
}
}
?>
\ No newline at end of file
<?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>.
*/
#namespace Doctrine::ORM::Mappings;
#use Doctrine::ORM::Entity;
/**
* A one-to-one mapping describes a uni-directional mapping from one entity
* to another entity.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @todo Rename to OneToOneMapping
*/
class Doctrine_Association_OneToOne extends Doctrine_Association
{
/**
* Maps the source foreign/primary key fields to the target primary/foreign key fields.
* i.e. source.id (pk) => target.user_id (fk).
* Reverse mapping of _targetToSourceKeyFields.
*/
protected $_sourceToTargetKeyColumns = array();
/**
* Maps the target primary/foreign key fields to the source foreign/primary key fields.
* i.e. target.user_id (fk) => source.id (pk).
* Reverse mapping of _sourceToTargetKeyFields.
*/
protected $_targetToSourceKeyColumns = array();
/**
* Constructor.
* Creates a new OneToOneMapping.
*
* @param array $mapping The mapping info.
*/
public function __construct(array $mapping)
{
parent::__construct($mapping);
if ($this->isOwningSide()) {
$this->_sourceToTargetKeyColumns = $mapping['joinColumns'];
$this->_targetToSourceKeyColumns = array_flip($this->_sourceToTargetKeyColumns);
}
}
/**
* Validates & completes the mapping. Mapping defaults are applied here.
*
* @param array $mapping The mapping to validate & complete.
* @return array The validated & completed mapping.
* @override
*/
protected function _validateMapping(array $mapping)
{
$mapping = parent::_validateMapping($mapping);
if ($this->isOwningSide()) {
if ( ! isset($mapping['joinColumns'])) {
throw Doctrine_MappingException::missingJoinColumns();
}
}
return $mapping;
}
/**
* Gets the source-to-target key column mapping.
*
* @return unknown
*/
public function getSourceToTargetKeyColumns()
{
return $this->_sourceToTargetKeyColumns;
}
/**
* Gets the target-to-source key column mapping.
*
* @return unknown
*/
public function getTargetToSourceKeyColumns()
{
return $this->_targetToSourceKeyColumns;
}
/**
* Lazy-loads the associated entity for a given entity.
*
* @param Doctrine::ORM::Entity $entity
* @return void
*/
public function lazyLoadFor(Doctrine_Entity $entity)
{
if ($entity->getClassName() != $this->_sourceClass->getClassName()) {
//error?
}
$dql = 'SELECT t.* FROM ' . $this->_targetClass->getClassName() . ' t WHERE ';
$params = array();
foreach ($this->_sourceToTargetKeyFields as $sourceKeyField => $targetKeyField) {
if ($params) {
$dql .= " AND ";
}
$dql .= "t.$targetKeyField = ?";
$params[] = $entity->_rawGetField($sourceKeyField);
}
$otherEntity = $this->_targetClass->getEntityManager()
->query($dql, $params)
->getFirst();
if ( ! $otherEntity) {
$otherEntity = Doctrine_Null::$INSTANCE;
}
$entity->_rawSetReference($this->_sourceFieldName, $otherEntity);
}
}
?>
\ No newline at end of file
This diff is collapsed.
......@@ -19,6 +19,8 @@
* <http://www.phpdoctrine.org>.
*/
#namespace Doctrine::ORM::Internal;
/**
* The metadata factory is used to create ClassMetadata objects that contain all the
* metadata of a class.
......@@ -31,10 +33,11 @@
* @version $Revision$
* @link www.phpdoctrine.org
* @since 2.0
* @todo Rename to ClassMetadataFactory.
*/
class Doctrine_ClassMetadata_Factory
{
protected $_conn;
protected $_em;
protected $_driver;
/**
......@@ -52,7 +55,7 @@ class Doctrine_ClassMetadata_Factory
*/
public function __construct(Doctrine_EntityManager $em, $driver)
{
$this->_conn = $em;
$this->_em = $em;
$this->_driver = $driver;
}
......@@ -101,7 +104,7 @@ class Doctrine_ClassMetadata_Factory
$class = $classes[$loadedParentClass];
} else {
$rootClassOfHierarchy = count($parentClasses) > 0 ? array_shift($parentClasses) : $name;
$class = new Doctrine_ClassMetadata($rootClassOfHierarchy, $this->_conn);
$class = new Doctrine_ClassMetadata($rootClassOfHierarchy, $this->_em);
$this->_loadMetadata($class, $rootClassOfHierarchy);
$classes[$rootClassOfHierarchy] = $class;
}
......@@ -115,7 +118,7 @@ class Doctrine_ClassMetadata_Factory
$parent = $class;
foreach ($parentClasses as $subclassName) {
$subClass = new Doctrine_ClassMetadata($subclassName, $this->_conn);
$subClass = new Doctrine_ClassMetadata($subclassName, $this->_em);
$subClass->setInheritanceType($parent->getInheritanceType(), $parent->getInheritanceOptions());
$this->_addInheritedFields($subClass, $parent);
$this->_addInheritedRelations($subClass, $parent);
......@@ -130,14 +133,10 @@ class Doctrine_ClassMetadata_Factory
protected function _addInheritedFields($subClass, $parentClass)
{
foreach ($parentClass->getFieldMappings() as $name => $definition) {
foreach ($parentClass->getFieldMappings() as $fieldName => $mapping) {
$fullName = "$name as " . $parentClass->getFieldName($name);
$definition['inherited'] = true;
$subClass->mapColumn(
$fullName,
$definition['type'],
$definition['length'],
$definition);
$mapping['inherited'] = true;
$subClass->mapField($mapping);
}
}
......@@ -163,6 +162,7 @@ class Doctrine_ClassMetadata_Factory
$names = array();
$className = $name;
// get parent classes
//TODO: Skip Entity types MappedSuperclass/Transient
do {
if ($className === 'Doctrine_Entity') {
break;
......@@ -182,11 +182,13 @@ class Doctrine_ClassMetadata_Factory
// load further metadata
$this->_driver->loadMetadataForClass($name, $class);
// set default table name, if necessary
$tableName = $class->getTableName();
if ( ! isset($tableName)) {
$class->setTableName(Doctrine::tableize($class->getClassName()));
}
// complete identifier mapping
$this->_initIdentifier($class);
return $class;
......@@ -199,7 +201,7 @@ class Doctrine_ClassMetadata_Factory
*/
protected function _initIdentifier(Doctrine_ClassMetadata $class)
{
switch (count((array)$class->getIdentifier())) {
/*switch (count($class->getIdentifier())) {
case 0: // No identifier in the class mapping yet
// If its a subclass, inherit the identifier from the parent.
......@@ -217,7 +219,7 @@ class Doctrine_ClassMetadata_Factory
}
// add all inherited primary keys
foreach ((array) $class->getIdentifier() as $id) {
foreach ($class->getIdentifier() as $id) {
$definition = $rootClass->getDefinitionOf($id);
// inherited primary keys shouldn't contain autoinc
......@@ -231,16 +233,7 @@ class Doctrine_ClassMetadata_Factory
$definition, true);
}
} else {
throw Doctrine_MappingException::identifierRequired($class->getClassName());
/* Legacy behavior of auto-adding an id field
$definition = array('type' => 'integer',
'length' => 20,
'autoincrement' => true,
'primary' => true);
$class->mapColumn('id', $definition['type'], $definition['length'], $definition, true);
$class->setIdentifier(array('id'));
$class->setIdentifierType(Doctrine::IDENTIFIER_AUTOINC);
*/
throw Doctrine_MappingException::identifierRequired($class->getClassName());
}
break;
case 1: // A single identifier is in the mapping
......@@ -293,6 +286,36 @@ class Doctrine_ClassMetadata_Factory
break;
default: // Multiple identifiers are in the mapping so its a composite id
$class->setIdentifierType(Doctrine::IDENTIFIER_COMPOSITE);
}*/
// If the chosen generator type is "auto", then pick the one appropriate for
// the database.
// FIXME: This is very ugly here. Such switch()es on the database driver
// are unnecessary as we can easily replace them with polymorphic calls on
// the connection (or another) object. We just need to decide where to put
// the id generation types.
if ($class->getIdGeneratorType() == Doctrine_ClassMetadata::GENERATOR_TYPE_AUTO) {
switch (strtolower($this->_em->getConnection()->getDriverName())) {
case 'mysql':
// pick IDENTITY
$class->setIdGeneratorType(Doctrine_ClassMetadata::GENERATOR_TYPE_IDENTITY);
break;
case 'oracle':
//pick SEQUENCE
break;
case 'postgres':
//pick SEQUENCE
break;
case 'firebird':
//pick what?
break;
case 'mssql':
//pick what?
default:
throw new Doctrine_Exception("Encountered unknown database driver: "
. $this->_em->getConnection()->getDriverName());
}
}
}
......
This diff is collapsed.
......@@ -138,8 +138,9 @@ abstract class Doctrine_Connection implements Countable
* @var array $properties
*/
protected $properties = array(
'sql_comments' => array(array('start' => '--', 'end' => "\n", 'escape' => false),
array('start' => '/*', 'end' => '*/', 'escape' => false)),
'sql_comments' => array(
array('start' => '--', 'end' => "\n", 'escape' => false),
array('start' => '/*', 'end' => '*/', 'escape' => false)),
'identifier_quoting' => array('start' => '"', 'end' => '"','escape' => '"'),
'string_quoting' => array('start' => "'", 'end' => "'", 'escape' => false,
'escape_pattern' => false),
......@@ -154,6 +155,8 @@ abstract class Doctrine_Connection implements Countable
/**
* The parameters used during creation of the Connection.
*
* @var array
*/
protected $_params = array();
......@@ -364,24 +367,24 @@ abstract class Doctrine_Connection implements Countable
//$this->getListener()->preConnect($event);
// TODO: the extension_loaded check can happen earlier, maybe in the factory
if (extension_loaded('pdo')) {
$driverOptions = isset($this->_params['driverOptions']) ?
$this->_params['driverOptions'] : array();
$user = isset($this->_params['user']) ?
$this->_params['user'] : null;
$password = isset($this->_params['password']) ?
$this->_params['password'] : null;
$this->_pdo = new PDO(
$this->_constructPdoDsn(),
$user,
$password,
$driverOptions
);
$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->_pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
} else {
if ( ! extension_loaded('pdo')) {
throw new Doctrine_Connection_Exception("Couldn't locate driver named " . $e[0]);
}
$driverOptions = isset($this->_params['driverOptions']) ?
$this->_params['driverOptions'] : array();
$user = isset($this->_params['user']) ?
$this->_params['user'] : null;
$password = isset($this->_params['password']) ?
$this->_params['password'] : null;
$this->_pdo = new PDO(
$this->_constructPdoDsn(),
$user,
$password,
$driverOptions
);
$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->_pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
// attach the pending attributes to adapter
/*foreach($this->pendingAttributes as $attr => $value) {
......@@ -427,9 +430,8 @@ abstract class Doctrine_Connection implements Countable
*/
public function supports($feature)
{
return (isset($this->supported[$feature])
&& ($this->supported[$feature] === 'emulated'
|| $this->supported[$feature]));
return (isset($this->supported[$feature]) &&
($this->supported[$feature] === 'emulated' || $this->supported[$feature]));
}
/**
......@@ -662,8 +664,7 @@ abstract class Doctrine_Connection implements Countable
}
/**
* quote
* quotes given input parameter
* Quotes given input parameter
*
* @param mixed $input parameter to be quoted
* @param string $type
......@@ -794,10 +795,9 @@ abstract class Doctrine_Connection implements Countable
$this->getAttribute(Doctrine::ATTR_LISTENER)->postPrepare($event);
return new Doctrine_Connection_Statement($this, $stmt);
} catch(Doctrine_Adapter_Exception $e) {
} catch(PDOException $e) { }
$this->rethrowException($e, $this);
} catch (PDOException $e) {
$this->rethrowException($e, $this);
}
}
/**
......@@ -859,10 +859,9 @@ abstract class Doctrine_Connection implements Countable
return $stmt;
}
} catch (Doctrine_Adapter_Exception $e) {
} catch (PDOException $e) { }
$this->rethrowException($e, $this);
} catch (PDOException $e) {
$this->rethrowException($e, $this);
}
}
/**
......@@ -894,10 +893,9 @@ abstract class Doctrine_Connection implements Countable
return $count;
}
} catch (Doctrine_Adapter_Exception $e) {
} catch (PDOException $e) { }
$this->rethrowException($e, $this);
} catch (PDOException $e) {
$this->rethrowException($e, $this);
}
}
/**
......@@ -923,7 +921,7 @@ abstract class Doctrine_Connection implements Countable
}
/**
* rethrowException
* Wraps the given exception into a driver-specific exception and rethrows it.
*
* @throws Doctrine_Connection_Exception
*/
......@@ -940,9 +938,7 @@ abstract class Doctrine_Connection implements Countable
}
$exc->processErrorInfo($e->errorInfo);
if ($this->getAttribute(Doctrine::ATTR_THROW_EXCEPTIONS)) {
throw $exc;
}
throw $exc;
//$this->getListener()->postError($event);
}
......@@ -1178,7 +1174,6 @@ abstract class Doctrine_Connection implements Countable
}
}
public function getFormatter()
{
if ( ! $this->modules['formatter']) {
......@@ -1186,4 +1181,25 @@ abstract class Doctrine_Connection implements Countable
}
return $this->modules['formatter'];
}
public function getSequenceModule()
{
if ( ! $this->modules['sequence']) {
$class = "Doctrine_Sequence_" . $this->_driverName;
$this->modules['sequence'] = new $class;
}
return $this->modules['sequence'];
}
/**
* Gets the default (preferred) Id generation strategy of the database platform.
*
* @todo Sure, the id generator types are more ORM functionality but they're
* still kind of dbal related. Maybe we need another set of classes (DatabasePlatform?)
* but im not so sure...
*/
/*abstract*/ public function getDefaultIdGeneratorType()
{
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -42,6 +42,10 @@
*/
class Doctrine_EntityManager
{
const FLUSHMODE_AUTO = 'auto';
const FLUSHMODE_COMMIT = 'commit';
const FLUSHMODE_MANUAL = 'manual';
/**
* The unique name of the EntityManager. The name is used to bind entity classes
* to certain EntityManagers.
......@@ -70,11 +74,11 @@ class Doctrine_EntityManager
private static $_flushModes = array(
// auto: Flush occurs automatically after each operation that issues database
// queries. No operations are queued.
'auto',
self::FLUSHMODE_AUTO,
// commit: Flush occurs automatically at transaction commit.
'commit',
self::FLUSHMODE_COMMIT,
// manual: Flush occurs never automatically.
'manual'
self::FLUSHMODE_MANUAL
);
/**
......@@ -242,7 +246,7 @@ class Doctrine_EntityManager
*/
public function detach(Doctrine_Entity $entity)
{
return $this->_unitOfWork->unregisterIdentity($entity);
return $this->_unitOfWork->removeFromIdentityMap($entity);
}
/**
......@@ -287,7 +291,7 @@ class Doctrine_EntityManager
*/
public function flush()
{
$this->_unitOfWork->flush();
$this->_unitOfWork->commit();
}
/**
......@@ -386,6 +390,9 @@ class Doctrine_EntityManager
public function save(Doctrine_Entity $entity)
{
$this->_unitOfWork->save($entity);
if ($this->_flushMode == self::FLUSHMODE_AUTO) {
$this->flush();
}
}
/**
......@@ -453,7 +460,7 @@ class Doctrine_EntityManager
return $entity;
} else {
$entity = new $className;
$this->_unitOfWork->registerIdentity($entity);
$this->_unitOfWork->addToIdentityMap($entity);
}
}
} else {
......@@ -469,6 +476,17 @@ class Doctrine_EntityManager
return $entity;
}
/**
* Checks if the instance is managed by the EntityManager.
*
* @return boolean
*/
public function contains(Doctrine_Entity $entity)
{
return $this->_unitOfWork->isInIdentityMap($entity) &&
! $this->_unitOfWork->isRegisteredRemoved($entity);
}
/**
* INTERNAL:
* For internal hydration purposes only.
......@@ -546,7 +564,7 @@ class Doctrine_EntityManager
}
/**
* Gets the COnfiguration used by the EntityManager.
* Gets the Configuration used by the EntityManager.
*
* @return Configuration
*/
......@@ -555,6 +573,16 @@ class Doctrine_EntityManager
return $this->_config;
}
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*
* @return Doctrine::ORM::UnitOfWork
*/
public function getUnitOfWork()
{
return $this->_unitOfWork;
}
}
?>
\ No newline at end of file
<?php
/*
* $Id$
* $Id: EventListener.php 4653 2008-07-10 17:17:58Z romanb $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
......@@ -20,18 +20,13 @@
*/
/**
* Doctrine_EventListener all event listeners extend this base class
* the empty methods allow child classes to only implement the methods they need to implement
*
* EventSubscriber.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @package Doctrine
* @subpackage EventListener
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 2.0
* @version $Revision$
* @todo Remove. The 2.0 event system has no listener interfaces.
* @version $Revision: 4653 $
*/
interface Doctrine_EventSubscriber
{
......
......@@ -31,7 +31,8 @@
* @since 1.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @author Roman Borschel <roman@code-factory.org>
* @todo Rename to DoctrineException
*/
class Doctrine_Exception extends Exception
{
......
......@@ -72,7 +72,7 @@ class Doctrine_Hydrator_RecordDriver
public function initRelatedCollection(Doctrine_Entity $entity, $name)
{
if ( ! isset($this->_initializedRelations[$entity->getOid()][$name])) {
$relation = $entity->getClassMetadata()->getRelation($name);
$relation = $entity->getClass()->getRelation($name);
$relatedClass = $relation->getTable();
$coll = $this->getElementCollection($relatedClass->getClassName());
$coll->setReference($entity, $relation);
......
......@@ -519,16 +519,12 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
* $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(Doctrine_ClassMetadata $class, $fieldName, $value, $typeHint = null)
{
......@@ -540,15 +536,17 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract
$type = is_null($typeHint) ? $class->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 'string':
case 'enum':
return $class->enumValue($fieldName, $value);
break;
case 'boolean':
return (boolean) $value;
// don't do any conversions on primitive types
break;
//case 'enum':
// return $class->enumValue($fieldName, $value);
//break;
//case 'boolean':
// return (boolean) $value;
//break;
case 'array':
case 'object':
if (is_string($value)) {
......
<?php
#namespace Doctrine::ORM::Internal;
/**
* The CommitOrderCalculator is used by the UnitOfWork to sort out the
* correct order in which changes to entities need to be persisted.
*
* @since 2.0
* @todo Rename to: CommitOrderCalculator
* @author Roman Borschel <roman@code-factory.org>
*/
class Doctrine_Internal_CommitOrderCalculator
{
private $_currentTime;
/**
* The node list used for sorting.
*
* @var array
*/
private $_nodes = array();
/**
* The topologically sorted list of items. Note that these are not nodes
* but the wrapped items.
*
* @var array
*/
private $_sorted;
/**
* Orders the given list of CommitOrderNodes based on their dependencies.
*
* Uses a depth-first search (DFS) to traverse the graph.
* The desired topological sorting is the reverse postorder of these searches.
*
* @param array $nodes The list of (unordered) CommitOrderNodes.
* @return array The list of ordered items. These are the items wrapped in the nodes.
*/
public function getCommitOrder()
{
// Check whether we need to do anything. 0 or 1 node is easy.
$nodeCount = count($this->_nodes);
if ($nodeCount == 0) {
return array();
} else if ($nodeCount == 1) {
$node = array_pop($this->_nodes);
return array($node->getClass());
}
$this->_sorted = array();
// Init
foreach ($this->_nodes as $node) {
$node->markNotVisited();
$node->setPredecessor(null);
}
$this->_currentTime = 0;
// Go
foreach ($this->_nodes as $node) {
if ($node->isNotVisited()) {
$node->visit();
}
}
return $this->_sorted;
}
public function addNode($key, $node)
{
$this->_nodes[$key] = $node;
}
public function addNodeWithItem($key, $item)
{
$this->_nodes[$key] = new Doctrine_Internal_CommitOrderNode($item, $this);
}
public function getNodeForKey($key)
{
return $this->_nodes[$key];
}
public function hasNodeWithKey($key)
{
return isset($this->_nodes[$key]);
}
public function clear()
{
$this->_nodes = array();
$this->_sorted = array();
}
public function getNextTime()
{
return ++$this->_currentTime;
}
public function prependNode($node)
{
array_unshift($this->_sorted, $node->getClass());
}
}
?>
\ No newline at end of file
<?php
#namespace Doctrine::ORM::Internal;
#use Doctrine::ORM::Mapping::ClassMetadata;
/**
* A CommitOrderNode is a temporary wrapper around ClassMetadata objects
* that is used to sort the order of commits.
*
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
*/
class Doctrine_Internal_CommitOrderNode
{
const NOT_VISITED = 1;
const IN_PROGRESS = 2;
const VISITED = 3;
private $_traversalState;
private $_predecessor;
private $_status;
private $_calculator;
private $_relatedNodes = array();
private $_discoveryTime;
private $_finishingTime;
private $_wrappedObj;
private $_relationEdges = array();
public function __construct($wrappedObj, Doctrine_Internal_CommitOrderCalculator $calc)
{
$this->_wrappedObj = $wrappedObj;
$this->_calculator = $calc;
}
public function getClass()
{
return $this->_wrappedObj;
}
public function setPredecessor($node)
{
$this->_predecessor = $node;
}
public function getPredecessor()
{
return $this->_predecessor;
}
public function markNotVisited()
{
$this->_traversalState = self::NOT_VISITED;
}
public function markInProgress()
{
$this->_traversalState = self::IN_PROGRESS;
}
public function markVisited()
{
$this->_traversalState = self::VISITED;
}
public function isNotVisited()
{
return $this->_traversalState == self::NOT_VISITED;
}
public function isInProgress()
{
return $this->_traversalState == self::IN_PROGRESS;
}
public function visit()
{
$this->markInProgress();
$this->setDiscoveryTime($this->_calculator->getNextTime());
foreach ($this->getRelatedNodes() as $node) {
if ($node->isNotVisited()) {
$node->setPredecessor($this);
$node->visit();
}
if ($node->isInProgress()) {
// back edge => cycle
//TODO: anything to do here?
}
}
$this->markVisited();
$this->_calculator->prependNode($this);
$this->setFinishingTime($this->_calculator->getNextTime());
}
public function setDiscoveryTime($time)
{
$this->_discoveryTime = $time;
}
public function setFinishingTime($time)
{
$this->_finishingTime = $time;
}
public function getDiscoveryTime()
{
return $this->_discoveryTime;
}
public function getFinishingTime()
{
return $this->_finishingTime;
}
public function getRelatedNodes()
{
return $this->_relatedNodes;
}
/**
* Adds a directed dependency (an edge). "$this -before-> $other".
*
* @param Doctrine_Internal_CommitOrderNode $node
*/
public function before(Doctrine_Internal_CommitOrderNode $node)
{
$this->_relatedNodes[] = $node;
}
}
?>
\ No newline at end of file
......@@ -12,6 +12,22 @@ class Doctrine_MappingException extends Doctrine_Exception
return new self("No identifier specified for Entity '$entityName'."
. " Every Entity must have an identifier.");
}
public static function invalidInheritanceType($type)
{
return new self("The inheritance type '$type' does not exist.");
}
public static function invalidInheritanceOption($name)
{
return new self("The inheritance option '$name' does not exist.");
}
public static function generatorNotAllowedWithCompositeId()
{
return new self("Id generators can't be used with a composite id.");
}
}
?>
\ No newline at end of file
......@@ -30,7 +30,7 @@
* @since 1.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @todo Really needed?
* @todo Really needed? Remove.
*/
interface Doctrine_Overloadable {
/**
......
......@@ -28,6 +28,7 @@
* @since 1.0
* @version $Revision$
* @author Joe Simms <joe.simms@websites4.com>
* @todo Move to NestedSet behavior.
*/
class Doctrine_Tree
{
......
......@@ -33,6 +33,7 @@
* @version $Revision$
* @author Roman Borschel <roman@code-factory.org>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @todo Move to validator package.
*/
class Doctrine_Validator
{
......
<?php
require_once 'lib/DoctrineTestInit.php';
class Orm_Associations_CascadeTest extends Doctrine_OrmTestCase
{
protected function setUp() {
;
}
protected function tearDown() {
;
}
public function testDeleteCascade()
{
$container = array();
$cascade = new DeleteCascade();
$cascade->cascade($entity, $container);
}
}
abstract class Cascade
{
public function cascade(Doctrine_Entity $record, array &$container)
{
if ($this->shouldCascadeTo($record)) {
$container[$record->getOid()] = $record;
}
foreach ($record->getTable()->getRelations() as $relation) {
if ($this->doCascade($relation)) {
$this->prepareCascade($record, $relation);
$relatedObjects = $record->get($relation->getAlias());
if ($relatedObjects instanceof Doctrine_Record && $this->shouldCascadeTo($relatedObjects)
&& ! isset($container[$relatedObjects->getOid()])) {
$this->cascade($relatedObjects, $container);
} else if ($relatedObjects instanceof Doctrine_Collection && count($relatedObjects) > 0) {
foreach ($relatedObjects as $object) {
if ( ! isset($container[$object->getOid()])) {
$this->cascade($object, $container);
}
}
}
}
}
}
}
class DeleteCascade extends Cascade
{
public function doCascade($relation)
{
return $relation->isCascadeDelete();
}
public function prepareCascade($record, $relation)
{
$fieldName = $relation->getAlias();
// if it's a xToOne relation and the related object is already loaded
// we don't need to refresh, else we need to.
if ( ! ($relation->getType() == Doctrine_Relation::ONE && isset($record->$fieldName))) {
$record->refreshRelated($relation->getAlias());
}
}
public function shouldCascadeTo(Doctrine_Entity $entity)
{
//TODO: also ignore removed Entities. incorporate that in exists() with a new
// state? (DELETED?)
return ! $entity->exists();
}
}
\ No newline at end of file
<?php
require_once 'lib/DoctrineTestInit.php';
class Orm_Associations_OneToOneMappingTest extends Doctrine_OrmTestCase
{
public function testCorrectOneToOneBidirectionalMapping()
{
$owningSideMapping = array(
'fieldName' => 'address',
'targetEntity' => 'Address',
'joinColumns' => array('address_id' => 'id'),
'sourceEntity' => 'Person' // This is normally filled by ClassMetadata
);
$oneToOneMapping = new Doctrine_Association_OneToOne($owningSideMapping);
$this->assertEquals(array('address_id' => 'id'), $oneToOneMapping->getSourceToTargetKeyColumns());
$this->assertEquals(array('id' => 'address_id'), $oneToOneMapping->getTargetToSourceKeyColumns());
$this->assertEquals('Address', $oneToOneMapping->getTargetEntityName());
$this->assertEquals('Person', $oneToOneMapping->getSourceEntityName());
$this->assertEquals('address', $oneToOneMapping->getSourceFieldName());
$this->assertTrue($oneToOneMapping->isOwningSide());
$inverseSideMapping = array(
'mappedBy' => 'address'
);
$oneToOneMapping = new Doctrine_Association_OneToOne($inverseSideMapping);
$this->assertEquals('address', $oneToOneMapping->getMappedByFieldName());
$this->assertTrue($oneToOneMapping->isInverseSide());
}
}
?>
\ No newline at end of file
......@@ -106,17 +106,17 @@ class Orm_Component_CollectionTest extends Doctrine_OrmTestCase
/**
* @test
*/
public function shouldSetKeyColumnWhenAddingNewRowAsArray()
/*public function shouldSetKeyColumnWhenAddingNewRowAsArray()
{
$this->assertTrue(isset($this->cmsColl['test']));
$this->assertEquals($this->cmsUser, $this->cmsColl['test']);
}
}*/
/**
* @test
*/
public function shouldSerializeAndUnserializeCollectionWithData()
/*public function shouldSerializeAndUnserializeCollectionWithData()
{
$serialized = serialize($this->cmsColl);
$coll = unserialize($serialized);
......@@ -126,6 +126,6 @@ class Orm_Component_CollectionTest extends Doctrine_OrmTestCase
$user = $coll['test'];
$this->assertTrue($user instanceOf CmsUser);
$this->assertEquals('test', $user['username']);
}
}*/
}
......@@ -22,12 +22,21 @@ class Orm_Entity_AccessorTest extends Doctrine_OrmTestCase
class CustomAccessorMutatorTestEntity extends Doctrine_Entity
{
public static function initMetadata($class)
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary'));
$class->mapColumn('username', 'string', 50, array(
'accessor' => 'getUsernameCustom',
'mutator' => 'setUsernameCustom'));
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 50,
'accessor' => 'getUsernameCustom',
'mutator' => 'setUsernameCustom'
));
}
public function getUsernameCustom()
......@@ -43,10 +52,19 @@ class CustomAccessorMutatorTestEntity extends Doctrine_Entity
class MagicAccessorMutatorTestEntity extends Doctrine_Entity
{
public static function initMetadata($class)
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary'));
$class->mapColumn('username', 'string', 50, array());
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 50
));
}
public function getUsername()
......
......@@ -22,10 +22,19 @@ class ConstructorTestEntity1 extends Doctrine_Entity
}
/* The mapping definition */
public static function initMetadata($class)
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary'));
$class->mapColumn('username', 'string', 50, array());
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 50
));
}
}
......
<?php
require_once 'lib/DoctrineTestInit.php';
require_once 'lib/mocks/Doctrine_EntityManagerMock.php';
require_once 'lib/mocks/Doctrine_ConnectionMock.php';
/**
* UnitOfWork tests.
* These tests run without a database through mocking the
* persister/connection/sequence used by the UnitOfWork.
*/
class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase
{
private $_unitOfWork;
private $_user;
// Mocks
// Provides a sequence mock to the UnitOfWork
private $_connectionMock;
// The sequence mock
private $_sequenceMock;
// The persister mock used by the UnitOfWork
private $_persisterMock;
// The EntityManager mock that provides the mock persister
private $_emMock;
protected function setUp() {
parent::setUp();
$this->_user = new ForumUser();
$this->_unitOfWork = new Doctrine_Connection_UnitOfWork($this->_em);
$this->_user->id = 1;
$this->_user->username = 'romanb';
$this->_connectionMock = new Doctrine_ConnectionMock(array());
$this->_sequenceMock = $this->_connectionMock->getSequenceModule();
$this->_emMock = new Doctrine_EntityManagerMock($this->_connectionMock);
$this->_persisterMock = $this->_emMock->getEntityPersister("ForumUser");
$this->_unitOfWork = $this->_emMock->getUnitOfWork();
}
protected function tearDown() {
$this->_user->free();
}
/* Basic registration tests */
public function testRegisterNew()
{
$this->_user->username = 'romanb';
$this->_user->id = 1;
// registerNew() is normally called in save()/persist()
$this->_unitOfWork->registerNew($this->_user);
$this->assertTrue($this->_unitOfWork->isRegisteredNew($this->_user));
$this->assertTrue($this->_unitOfWork->contains($this->_user));
$this->assertTrue($this->_unitOfWork->isInIdentityMap($this->_user));
$this->assertFalse($this->_unitOfWork->isRegisteredDirty($this->_user));
$this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user));
}
/*public function testRegisterNewPerf() {
$s = microtime(true);
for ($i=1; $i<40000; $i++) {
$user = new ForumUser();
$user->id = $i;
$this->_unitOfWork->registerNew($user);
}
$e = microtime(true);
echo $e - $s . " seconds" . PHP_EOL;
}*/
public function testRegisterDirty()
{
$this->_user->username = 'romanb';
$this->_user->id = 1;
$this->assertEquals(Doctrine_Entity::STATE_TDIRTY, $this->_user->_state());
$this->assertFalse($this->_unitOfWork->contains($this->_user));
$this->assertEquals(Doctrine_Entity::STATE_NEW, $this->_user->_state());
$this->assertFalse($this->_unitOfWork->isInIdentityMap($this->_user));
$this->_unitOfWork->registerDirty($this->_user);
$this->assertTrue($this->_unitOfWork->isRegisteredDirty($this->_user));
$this->assertFalse($this->_unitOfWork->isRegisteredNew($this->_user));
$this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user));
}
public function testRegisterRemovedOnTransientEntityIsIgnored()
public function testRegisterRemovedOnNewEntityIsIgnored()
{
$this->_user->username = 'romanb';
$this->_user->id = 1;
$this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user));
$this->_unitOfWork->registerRemoved($this->_user);
$this->_unitOfWork->registerDeleted($this->_user);
$this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user));
}
/*public function testSavedEntityHasIdentityAndIsManaged()
/* Operational tests */
public function testSavingSingleEntityWithIdentityColumnForcesInsert()
{
$this->_user->username = 'romanb';
$this->_user->save();
$this->assertTrue($this->_unitOfWork->hasIdentity($this->_user));
$this->assertTrue($this->_unitOfWork->isManaged($this->_user));
}*/
$this->assertEquals(Doctrine_Entity::STATE_NEW, $this->_user->_state());
$this->_unitOfWork->save($this->_user);
$this->assertEquals(1, count($this->_persisterMock->getInserts())); // insert forced
$this->assertEquals(0, count($this->_persisterMock->getUpdates()));
$this->assertEquals(0, count($this->_persisterMock->getDeletes()));
$this->assertTrue($this->_unitOfWork->isInIdentityMap($this->_user));
$this->assertEquals(Doctrine_Entity::STATE_MANAGED, $this->_user->_state());
// should no longer be scheduled for insert
$this->assertFalse($this->_unitOfWork->isRegisteredNew($this->_user));
// should have an id
$this->assertTrue(is_numeric($this->_user->id));
// Now lets check whether a subsequence commit() does anything
$this->_persisterMock->reset();
$this->_unitOfWork->commit(); // shouldnt do anything
// verify that nothing happened
$this->assertEquals(0, count($this->_persisterMock->getInserts()));
$this->assertEquals(0, count($this->_persisterMock->getUpdates()));
$this->assertEquals(0, count($this->_persisterMock->getDeletes()));
}
public function testSavingSingleEntityWithSequenceIdGeneratorSchedulesInsert()
{
//...
}
public function testSavingSingleEntityWithTableIdGeneratorSchedulesInsert()
{
//...
}
public function testSavingSingleEntityWithSingleNaturalIdForcesInsert()
{
//...
}
public function testSavingSingleEntityWithCompositeIdForcesInsert()
{
//...
}
public function testSavingEntityGraphWithIdentityColumnsForcesInserts()
{
//...
}
public function testSavingEntityGraphWithSequencesDelaysInserts()
{
//...
}
public function testSavingEntityGraphWithNaturalIdsForcesInserts()
{
//...
}
public function testSavingEntityGraphWithMixedIdGenerationStrategies()
{
//...
}
}
\ No newline at end of file
<?php
require_once 'lib/mocks/Doctrine_SequenceMock.php';
class Doctrine_ConnectionMock extends Doctrine_Connection
{
protected $_driverName = 'Mysql';
private $_sequenceModuleMock;
public function getSequenceModule()
{
if ( ! $this->_sequenceModuleMock) {
$this->_sequenceModuleMock = new Doctrine_SequenceMock($this);
}
return $this->_sequenceModuleMock;
}
}
?>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment