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
......@@ -21,6 +21,8 @@
#namespace Doctrine::ORM::Mapping;
#use Doctrine::ORM::EntityManager;
/**
* A <tt>ClassMetadata</tt> instance holds all the information (metadata) of an entity and
* it's associations and how they're mapped to the relational database.
......@@ -28,11 +30,50 @@
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
* @todo Rename to ClassDescriptor?
*/
class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
{
/* The inheritance mapping types */
const INHERITANCE_TYPE_NONE = 'none';
const INHERITANCE_TYPE_JOINED = 'joined';
const INHERITANCE_TYPE_SINGLE_TABLE = 'singleTable';
const INHERITANCE_TYPE_TABLE_PER_CLASS = 'tablePerClass';
/**
* Inheritance types enumeration.
*
* @var array
*/
protected static $_inheritanceTypes = array(
self::INHERITANCE_TYPE_NONE,
self::INHERITANCE_TYPE_JOINED,
self::INHERITANCE_TYPE_SINGLE_TABLE,
self::INHERITANCE_TYPE_TABLE_PER_CLASS
);
/* The Id generator types. TODO: belongs more in a DBAL class */
const GENERATOR_TYPE_AUTO = 'auto';
const GENERATOR_TYPE_SEQUENCE = 'sequence';
const GENERATOR_TYPE_TABLE = 'table';
const GENERATOR_TYPE_IDENTITY = 'identity';
const GENERATOR_TYPE_NONE = 'none';
/**
* Id Generator types enumeration.
*
* @var array
*/
protected static $_generatorTypes = array(
self::GENERATOR_TYPE_AUTO,
self::GENERATOR_TYPE_SEQUENCE,
self::GENERATOR_TYPE_TABLE,
self::GENERATOR_TYPE_IDENTITY,
self::GENERATOR_TYPE_NONE
);
/**
* The name of the entity class that is mapped to the database with this metadata.
* The name of the entity class.
*
* @var string
*/
......@@ -41,7 +82,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* The name of the entity class that is at the root of the entity inheritance
* hierarchy. If the entity is not part of an inheritance hierarchy this is the same
* as the $_entityName.
* as $_entityName.
*
* @var string
*/
......@@ -56,18 +97,23 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
protected $_customRepositoryClassName;
/**
* The EntityManager.
*
* @var Doctrine_Connection
* @var Doctrine::ORM::EntityManager
*/
protected $_em;
/**
* The names of the parent classes (ancestors).
*
* @var array
*/
protected $_parentClasses = array();
/**
* The names of all subclasses
* The names of all subclasses.
*
* @var array
*/
protected $_subClasses = array();
......@@ -80,20 +126,18 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
protected $_identifier = array();
/**
* The identifier type of the class.
* The inheritance mapping type used by the class.
*
* @see Doctrine::IDENTIFIER_* constants
* @var integer
*/
protected $_identifierType;
protected $_inheritanceType = self::INHERITANCE_TYPE_NONE;
/**
* The inheritance mapping type used by the class.
* The Id generator type used by the class.
*
*
* @var integer
* @var string
*/
protected $_inheritanceType = Doctrine::INHERITANCE_TYPE_NONE;
protected $_generatorType = self::GENERATOR_TYPE_NONE;
/**
* An array containing all behaviors attached to the class.
......@@ -102,30 +146,51 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @var array $_templates
* @todo Unify under 'Behaviors'.
*/
protected $_behaviors = array();
/**
* An array containing all behavior generators attached to the class.
*
* @see Doctrine_Record_Generator
* @var array $_generators
* @todo Unify under 'Behaviors'.
*/
protected $_generators = array();
//protected $_behaviors = array();
/**
* The field mappings of the class.
* Keys are field names and values are mapping definitions.
*
* The mapping definition array has at least the following values:
* The mapping definition array has the following values:
*
* - <b>fieldName</b> (string)
* The name of the field in the Entity.
*
* - <b>type</b> (string)
* The database type of the column. Can be one of Doctrine's portable types.
*
* - <b>columnName</b> (string, optional)
* The column name. Optional. Defaults to the field name.
*
* - <b>length</b> (integer, optional)
* The database length of the column. Optional. Defaults to Doctrine's
* default values for the portable types.
*
* - <b>id</b> (boolean, optional)
* Marks the field as the primary key of the Entity. Multiple fields of an
* entity can have the id attribute, forming a composite key.
*
* - <b>generatorType</b> (string, optional, requires: id)
* The generation type used for the field. Optional. Can only be applied on
* fields that are marked with the 'id' attribute. The possible values are:
* auto, identity, sequence, table.
*
* - <b>generator</b> (array, optional, requires: generationType=table|sequence)
* The generator options for a table or sequence generator. Can only be applied
* on fields that have a generationType of 'table' or 'sequence'.
*
* -- type the column type, eg. 'integer'
* -- length the column length, eg. 11
* - <b>nullable</b> (boolean, optional)
* Whether the column is nullable. Defaults to TRUE.
*
* additional keys:
* -- notnull whether or not the column is marked as notnull
* -- values enum values
* ... many more
* - <b>columnDefinition</b> (string, optional)
* The SQL fragment that is used when generating the DDL for the column.
*
* - <b>precision</b> (integer, optional)
* The precision of a decimal column. Only valid if the column type is decimal.
*
* - <b>scale</b> (integer, optional)
* The scale of a decimal column. Only valid if the column type is decimal.
*
* @var array
*/
......@@ -137,7 +202,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @var array
* @TODO Implementation (Value Object support)
*/
protected $_mappedEmbeddedValues = array();
//protected $_embeddedValueMappings = array();
/**
* Enter description here...
......@@ -180,62 +245,14 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
*/
//protected $_subclassFieldNames = array();
/**
* Caches enum value mappings. Keys are field names and values arrays with the
* mapping.
*/
protected $_enumValues = array();
/**
* Tree object associated with the class.
*
* @var Doctrine_Tree
* @todo Belongs to the NestedSet Behavior plugin.
*/
protected $_tree;
/**
* Cached column count, Doctrine_Entity uses this column count when
* determining its state.
*
* @var integer
*/
//protected $_columnCount;
/**
* Whether or not this class has default values.
*
* @var boolean
*/
protected $_hasDefaultValues;
/**
* Relation parser object. Manages the relations for the class.
*
* @var Doctrine_Relation_Parser $_parser
* @todo Remove.
*/
protected $_parser;
/**
* Enum value arrays.
*/
protected $_enumMap = array();
/**
* @var array $options an array containing all options
*
* -- treeImpl the tree implementation of this table (if any)
*
* -- treeOptions the tree options
*
* -- queryParts the bound query parts
*/
protected $_options = array(
'treeImpl' => null,
'treeOptions' => null,
'queryParts' => array()
);
/**
* Inheritance options.
*/
......@@ -277,11 +294,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
'checks' => array()
);
/**
* @var array $_invokedMethods method invoker cache
*/
protected $_invokedMethods = array();
/**
* The cached lifecycle listeners. There is only one instance of each
* listener class at any time.
......@@ -304,6 +316,19 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
*/
protected $_lifecycleListeners = array();
/**
* The association mappings.
*
* @var array
*/
protected $_associationMappings = array();
/**
* Flag indicating whether the identifier/primary key of the class is composite.
*
* @var boolean
*/
protected $_isIdentifierComposite = false;
/**
* Constructs a new ClassMetadata instance.
......@@ -315,11 +340,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
$this->_entityName = $entityName;
$this->_rootEntityName = $entityName;
$this->_em = $em;
$this->_parser = new Doctrine_Relation_Parser($this);
}
/**
*
* @deprecated
*/
public function getConnection()
{
......@@ -370,21 +396,21 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
*/
public function isIdentifier($fieldName)
{
if ($this->_identifierType != Doctrine::IDENTIFIER_COMPOSITE) {
if ( ! $this->_isIdentifierComposite) {
return $fieldName === $this->_identifier[0];
}
return in_array($fieldName, $this->_identifier);
}
/**
* Check if the field is unique
* Check if the class has a composite identifier.
*
* @param string $fieldName The field name
* @return boolean TRUE if the field is unique, FALSE otherwise.
* @return boolean TRUE if the identifier is composite, FALSE otherwise.
*/
public function isIdentifierComposite()
{
return $this->_identifierType == Doctrine::IDENTIFIER_COMPOSITE;
return $this->_isIdentifierComposite;
}
/**
......@@ -490,7 +516,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
return $this->_tableOptions[$name];
}
public function getBehaviorForMethod($method)
/*public function getBehaviorForMethod($method)
{
return (isset($this->_invokedMethods[$method])) ?
$this->_invokedMethods[$method] : false;
......@@ -498,10 +524,9 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
public function addBehaviorMethod($method, $behavior)
{
$this->_invokedMethods[$method] = $class;
}
}*/
/**
* getOption
* returns the value of given option
*
* @param string $name the name of the option
......@@ -562,8 +587,11 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
*/
public function getFieldMapping($fieldName)
{
return isset($this->_fieldMappings[$fieldName]) ?
$this->_fieldMappings[$fieldName] : false;
if ( ! isset($this->_fieldMappings[$fieldName])) {
throw Doctrine_MappingException::mappingNotFound($fieldName);
}
return $this->_fieldMappings[$fieldName];
}
/**
......@@ -575,7 +603,21 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
*/
public function getAssociationMapping($fieldName)
{
//...
if ( ! isset($this->_associationMappings[$fieldName])) {
throw Doctrine_MappingException::mappingNotFound($fieldName);
}
return $this->_associationMappings[$fieldName];
}
/**
* Gets all association mappings of the class.
*
* @return array
*/
public function getAssociationMappings()
{
return $this->_associationMappings;
}
/**
......@@ -626,7 +668,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* during hydration because the hydrator caches effectively.
*
* @return string The field name.
* @throws Doctrine::ORM::Exceptions::ClassMetadataException if the field name could
* @throws Doctrine::ORM::Exceptions::ClassMetadataException If the field name could
* not be found.
*/
public function lookupFieldName($lcColumnName)
......@@ -651,88 +693,93 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
}
/**
* Maps a field of the class to a database column.
*
* @param string $name The name of the column to map. Syntax: columnName [as propertyName].
* The property name is optional. If not used the column will be
* mapped to a property with the same name.
* @param string $type The type of the column.
* @param integer $length The length of the column.
* @param mixed $options
* @param boolean $prepend Whether to prepend or append the new column to the column list.
* By default the column gets appended.
* Adds a field mapping.
*
* @throws Doctrine_ClassMetadata_Exception If trying use wrongly typed parameter.
* @todo Rename to mapField()/addFieldMapping().
* @param array $mapping
*/
public function mapColumn($name, $type, $length = null, $options = array())
public function mapField(array $mapping)
{
// converts 0 => 'primary' to 'primary' => true etc.
foreach ($options as $k => $option) {
if (is_numeric($k)) {
if ( ! empty($option) && $option !== false) {
$options[$option] = true;
}
unset($options[$k]);
$mapping = $this->_validateAndCompleteFieldMapping($mapping);
if (isset($this->_fieldMappings[$mapping['fieldName']])) {
throw Doctrine_MappingException::duplicateFieldMapping();
}
$this->_fieldMappings[$mapping['fieldName']] = $mapping;
}
// extract column name & field name & lowercased column name
$parts = explode(' as ', $name);
if (count($parts) > 1) {
$fieldName = $parts[1];
} else {
$fieldName = $parts[0];
/**
* Overrides an existant field mapping.
* Used i.e. by Entity classes deriving from another Entity class that acts
* as a mapped superclass to refine the basic mapping.
*
* @param array $newMapping
* @todo Implementation.
*/
public function overrideFieldMapping(array $newMapping)
{
//...
}
$columnName = $parts[0];
$lcColumnName = strtolower($parts[0]);
if (isset($this->_fieldMappings[$fieldName])) {
return;
/**
* Validates & completes the field mapping. Default values are applied here.
*
* @param array $mapping
* @return array
*/
private function _validateAndCompleteFieldMapping(array $mapping)
{
// Check mandatory fields
if ( ! isset($mapping['fieldName'])) {
throw Doctrine_MappingException::missingFieldName();
}
if ( ! isset($mapping['type'])) {
throw Doctrine_MappingException::missingType();
}
// Fill column name <-> field name lookup maps
$this->_columnNames[$fieldName] = $columnName;
$this->_fieldNames[$columnName] = $fieldName;
$this->_lcColumnToFieldNames[$lcColumnName] = $fieldName;
$this->_lcColumnToFieldNames[$lcColumnName] = $fieldName;
// Complete fieldName and columnName mapping
if ( ! isset($mapping['columnName'])) {
$mapping['columnName'] = $mapping['fieldName'];
}
$lcColumnName = strtolower($mapping['columnName']);
// Inspect & fill $options
$this->_columnNames[$mapping['fieldName']] = $mapping['columnName'];
$this->_fieldNames[$mapping['columnName']] = $mapping['fieldName'];
$this->_lcColumnToFieldNames[$lcColumnName] = $mapping['fieldName'];
if ($length == null) {
$length = $this->_getDefaultLength($type);
// Complete length mapping
if ( ! isset($mapping['length'])) {
$mapping['length'] = $this->_getDefaultLength($mapping['type']);
}
$options['type'] = $type;
$options['length'] = $length;
if ( ! $this->_hasDefaultValues && isset($options['default'])) {
$this->_hasDefaultValues = true;
// Complete id mapping
if (isset($mapping['id']) && $mapping['id'] === true) {
if ( ! in_array($mapping['fieldName'], $this->_identifier)) {
$this->_identifier[] = $mapping['fieldName'];
}
if (isset($mapping['generatorType'])) {
if ( ! in_array($mapping['generatorType'], self::$_generatorTypes)) {
throw Doctrine_MappingException::invalidGeneratorType($mapping['generatorType']);
} else if (count($this->_identifier) > 1) {
throw Doctrine_MappingException::generatorNotAllowedWithCompositeId();
}
if ( ! empty($options['primary'])) {
if ( ! in_array($fieldName, $this->_identifier)) {
$this->_identifier[] = $fieldName;
$this->_generatorType = $mapping['generatorType'];
}
/*if (isset($options['autoincrement']) && $options['autoincrement'] === true) {
// TODO: validate/complete 'generator' mapping
}*/
// Check for composite key
if ( ! $this->_isIdentifierComposite && count($this->_identifier) > 1) {
$this->_isIdentifierComposite = true;
}
/*
if ( ! isset($options['immutable'])) {
$options['immutable'] = false;
}*/
$this->_fieldMappings[$fieldName] = $options;
}
$this->_columnCount++;
return $mapping;
}
/**
* Gets the default length for a field type.
* Gets the default length for a column type.
*
* @param unknown_type $type
* @return unknown
* @param string $type
* @return mixed
*/
private function _getDefaultLength($type)
{
......@@ -777,39 +824,10 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @param string The field name.
* @return array The names of all validators that are applied on the specified field.
*/
public function getFieldValidators($fieldName)
/*public function getFieldValidators($fieldName)
{
return isset($this->_fieldMappings[$fieldName]['validators']) ?
$this->_fieldMappings[$fieldName]['validators'] : array();
}
/**
* Checks whether the mapped class has a default value on any field.
*
* @return boolean TRUE if the entity has a default value on any field, otherwise false.
*/
/*public function hasDefaultValues()
{
return $this->_hasDefaultValues;
}*/
/**
* getDefaultValueOf
* returns the default value(if any) for given field
*
* @param string $fieldName
* @return mixed
*/
/*public function getDefaultValueOf($fieldName)
{
if ( ! isset($this->_fieldMappings[$fieldName])) {
throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$fieldName." doesn't exist.");
}
if (isset($this->_fieldMappings[$fieldName]['default'])) {
return $this->_fieldMappings[$fieldName]['default'];
} else {
return null;
}
}*/
/**
......@@ -833,41 +851,29 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
return $this->_identifier;
}
public function setIdentifier(array $identifier)
{
$this->_identifier = $identifier;
}
/**
* Gets the type of the identifier (primary key) used by the mapped class. The type
* can be either
* <tt>Doctrine::IDENTIFIER_NATURAL</tt>,
* <tt>Doctrine::IDENTIFIER_AUTOINCREMENT</tt>,
* <tt>Doctrine::IDENTIFIER_SEQUENCE</tt> or
* <tt>Doctrine::IDENTIFIER_COMPOSITE</tt>.
* Gets the name of the single id field. Note that this only works on
* entity classes that have a single-field pk.
*
* @return integer
* @return string
*/
public function getIdentifierType()
public function getSingleIdentifierFieldName()
{
return $this->_identifierType;
if ($this->_isIdentifierComposite) {
throw new Doctrine_Exception("Calling getSingleIdentifierFieldName "
. "on a class that uses a composite identifier is not allowed.");
}
/**
* Sets the identifier type used by the mapped class.
*/
public function setIdentifierType($type)
{
$this->_identifierType = $type;
return $this->_identifier[0];
}
public function hasMappedColumn($columnName)
public function setIdentifier(array $identifier)
{
return isset($this->_fieldNames[$columnName]);
$this->_identifier = $identifier;
}
/**
* hasField
* Checks whether the class has a (mapped) field with a certain name.
*
* @return boolean
*/
public function hasField($fieldName)
......@@ -875,87 +881,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
return isset($this->_columnNames[$fieldName]);
}
/**
* @param string $fieldName
* @return array
*/
/*public function getEnumValues($fieldName)
{
if (isset($this->_fieldMappings[$fieldName]['values'])) {
return $this->_fieldMappings[$fieldName]['values'];
} else {
return array();
}
}*/
/**
* enumValue
*
* @param string $field
* @param integer $index
* @return mixed
*/
/*public function enumValue($fieldName, $index)
{
if ($index instanceof Doctrine_Null) {
return $index;
}
if (isset($this->_enumValues[$fieldName][$index])) {
return $this->_enumValues[$fieldName][$index];
}
$columnName = $this->getColumnName($fieldName);
if ( ! $this->_em->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM) &&
isset($this->_fieldMappings[$fieldName]['values'][$index])) {
$enumValue = $this->_fieldMappings[$fieldName]['values'][$index];
} else {
$enumValue = $index;
}
$this->_enumValues[$fieldName][$index] = $enumValue;
return $enumValue;
}*/
/**
* enumIndex
*
* @param string $field
* @param mixed $value
* @return mixed
*/
/*public function enumIndex($fieldName, $value)
{
$values = $this->getEnumValues($fieldName);
$index = array_search($value, $values);
if ($index === false || ! $this->_em->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) {
return $index;
}
return $value;
}*/
/**
* getColumnCount
*
* @return integer the number of columns in this table
* @deprecated
*/
/*public function getColumnCount()
{
return $this->_columnCount;
}*/
/**
* getMappedColumnCount
*
* @return integer the number of mapped columns in the class.
*/
public function getMappedFieldCount()
{
return $this->_columnCount;
}
/**
* Gets the custom accessor of a field.
*
......@@ -983,7 +908,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* Gets all field mappings.
*
* @return unknown
* @return array
*/
public function getFieldMappings()
{
......@@ -991,28 +916,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
}
/**
* removeColumn
* removes given column
*
* @return boolean
*/
/*public function removeColumn($fieldName)
{
$columnName = array_search($fieldName, $this->_fieldNames);
unset($this->_fieldNames[$columnName]);
if (isset($this->_fieldMappings[$fieldName])) {
unset($this->_fieldMappings[$fieldName]);
return true;
}
$this->_columnCount--;
return false;
}*/
/**
* returns an array containing all the column names.
* Gets an array containing all the column names.
*
* @return array
*/
......@@ -1025,13 +929,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
foreach ($fieldNames as $fieldName) {
$columnNames[] = $this->getColumnName($fieldName);
}
return $columnNames;
}
}
/**
* returns an array with all the identifier column names.
* Returns an array with all the identifier column names.
*
* @return array
*/
......@@ -1041,7 +944,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
}
/**
* returns an array containing all the field names.
* Returns an array containing all the field names.
*
* @return array
*/
......@@ -1051,17 +954,65 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
}
/**
* getDefinitionOf
* Gets the Id generator type used by the class.
*
* @return mixed array on success, false on failure
* @deprecated
* @return string
*/
/*public function getDefinitionOf($fieldName)
public function getIdGeneratorType()
{
$columnName = $this->getColumnName($fieldName);
return $this->_generatorType;
}
return $this->getColumnDefinition($columnName);
}*/
/**
* Checks whether the class uses an Id generator.
*
* @return boolean TRUE if the class uses an Id generator, FALSE otherwise.
*/
public function usesIdGenerator()
{
return $this->_generatorType != self::GENERATOR_TYPE_NONE;
}
/**
* Checks whether the class uses an identity column for the Id generation.
*
* @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
*/
public function isIdGeneratorIdentity()
{
return $this->_generatorType == self::GENERATOR_TYPE_IDENTITY;
}
/**
* Checks whether the class uses a sequence for id generation.
*
* @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
*/
public function isIdGeneratorSequence()
{
return $this->_generatorType == self::GENERATOR_TYPE_SEQUENCE;
}
/**
* Checks whether the class uses a table for id generation.
*
* @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
*/
public function isIdGeneratorTable()
{
$this->_generatorType == self::GENERATOR_TYPE_TABLE;
}
/**
* Checks whether the class has a natural identifier/pk (which means it does
* not use any Id generator.
*
* @return boolean
*/
public function isIdentifierNatural()
{
return $this->_generatorType == self::GENERATOR_TYPE_NONE;
}
/**
* Gets the type of a field.
......@@ -1127,10 +1078,10 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @return array an array containing all templates
* @todo Unify under 'Behaviors'
*/
public function getBehaviors()
/*public function getBehaviors()
{
return $this->_behaviors;
}
}*/
/**
* Gets the inheritance type used by the class.
......@@ -1186,6 +1137,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* Sets the parent class names.
* Assumes that the class names in the passed array are in the order:
* directParent -> directParentParent -> directParentParentParent ... -> root.
*/
public function setParentClasses(array $classNames)
{
......@@ -1213,20 +1166,17 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
public function setInheritanceType($type, array $options = array())
{
if ($parentClassNames = $this->getParentClasses()) {
if ($this->_em->getClassMetadata($parentClassNames[0])->getInheritanceType() != $type) {
throw new Doctrine_ClassMetadata_Exception("All classes in an inheritance hierarchy"
. " must share the same inheritance mapping type. Mixing is not allowed.");
throw new Doctrine_MappingException("All classes in an inheritance hierarchy"
. " must share the same inheritance mapping type and this type must be set"
. " in the root class of the hierarchy.");
}
if ( ! in_array($type, self::$_inheritanceTypes)) {
throw Doctrine_MappingException::invalidInheritanceType($type);
}
if ($type == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) {
$this->_checkRequiredDiscriminatorOptions($options);
} else if ($type == Doctrine::INHERITANCE_TYPE_JOINED) {
if ($type == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE ||
$type == Doctrine::INHERITANCE_TYPE_JOINED) {
$this->_checkRequiredDiscriminatorOptions($options);
} else if ($type == Doctrine::INHERITANCE_TYPE_TABLE_PER_CLASS) {
;
} else {
throw new Doctrine_ClassMetadata_Exception("Invalid inheritance type '$type'.");
}
$this->_inheritanceType = $type;
......@@ -1284,6 +1234,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'.");
}
if ($this->_inheritanceType == 'joined' || $this->_inheritanceType == 'singleTable') {
switch ($name) {
case 'discriminatorColumn':
if ($value !== null && ! is_string($value)) {
......@@ -1297,6 +1248,10 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
. " must be an array.");
}
break;
// ... further validation checks as needed
default:
throw Doctrine_MappingException::invalidInheritanceOption($name);
}
}
$this->_inheritanceOptions[$name] = $value;
......@@ -1438,135 +1393,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
'options' => array_merge($options, $this->getTableOptions()));
}
/**
* getTemplate
*
* @param string $template
* @return void
* @todo Unify under 'Behaviors'.
*/
public function getBehavior($behaviorName)
{
if ( ! isset($this->_behaviors[$behaviorName])) {
throw new Doctrine_Table_Exception('Template ' . $behaviorName . ' not loaded');
}
return $this->_behaviors[$behaviorName];
}
/**
* @todo Unify under 'Behaviors'.
*/
public function hasBehavior($behaviorName)
{
return isset($this->_behaviors[$behaviorName]);
}
/**
* @todo Unify under 'Behaviors'.
*/
public function addBehavior($behaviorName, Doctrine_Template $impl)
{
$this->_behaviors[$behaviorName] = $impl;
return $this;
}
/**
* @todo Unify under 'Behaviors'.
*/
public function getGenerators()
{
return $this->_generators;
}
/**
* @todo Unify under 'Behaviors'.
*/
public function getGenerator($generator)
{
if ( ! isset($this->_generators[$generator])) {
throw new Doctrine_Table_Exception('Generator ' . $generator . ' not loaded');
}
return $this->_generators[$plugin];
}
/**
* @todo Unify under 'Behaviors'.
*/
public function hasGenerator($generator)
{
return isset($this->_generators[$generator]);
}
/**
* @todo Unify under 'Behaviors'.
*/
public function addGenerator(Doctrine_Record_Generator $generator, $name = null)
{
if ($name === null) {
$this->_generators[] = $generator;
} else {
$this->_generators[$name] = $generator;
}
return $this;
}
/**
* loadBehavior
*
* @param string $template
* @todo Unify under 'Behaviors'.
*/
public function loadBehavior($behavior, array $options = array())
{
$this->actAs($behavior, $options);
}
/**
* @todo Unify under 'Behaviors'.
*/
public function loadGenerator(Doctrine_Record_Generator $generator)
{
$generator->initialize($this->_table);
$this->addGenerator($generator, get_class($generator));
}
/**
* getTree
*
* getter for associated tree
*
* @return mixed if tree return instance of Doctrine_Tree, otherwise returns false
* @todo Belongs to the NestedSet Behavior.
*/
public function getTree()
{
if ($this->getOption('treeImpl')) {
if ( ! $this->_tree) {
$options = $this->getOption('treeOptions') ? $this->getOption('treeOptions') : array();
$this->_tree = Doctrine_Tree::factory($this,
$this->getOption('treeImpl'), $options);
}
return $this->_tree;
}
return false;
}
/**
* isTree
*
* determine if table acts as tree
*
* @return mixed if tree return true, otherwise returns false
* @todo Belongs to the NestedSet Behavior.
*/
public function isTree()
{
return ( ! is_null($this->getOption('treeImpl'))) ? true : false;
}
/**
* Checks whether a persistent field is inherited from a superclass.
*
......@@ -1577,47 +1403,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
return isset($this->_fieldMappings[$fieldName]['inherited']);
}
/**
* bindQueryParts
* binds query parts to given component
*
* @param array $queryParts an array of pre-bound query parts
* @return Doctrine_Entity this object
*/
public function bindQueryParts(array $queryParts)
{
$this->_options['queryParts'] = $queryParts;
return $this;
}
/**
* bindQueryPart
* binds given value to given query part
*
* @param string $queryPart
* @param mixed $value
* @return Doctrine_Entity this object
*/
public function bindQueryPart($queryPart, $value)
{
$this->_options['queryParts'][$queryPart] = $value;
return $this;
}
/**
* getBoundQueryPart
*
* @param string $queryPart
* @return string $queryPart
*/
public function getBoundQueryPart($queryPart)
{
if ( ! isset($this->_options['queryParts'][$queryPart])) {
return null;
}
return $this->_options['queryParts'][$queryPart];
}
/**
* Sets the name of the primary table the class is mapped to.
*
......@@ -1657,17 +1442,23 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
}
/**
* Adds a one-to-one association mapping.
*
* @todo Implementation.
*/
public function oneToOne($targetEntity, $definition)
public function mapOneToOne(array $mapping)
{
$oneToOneMapping = new Doctrine_Association_OneToOne($mapping);
if (isset($this->_associationMappings[$oneToOneMapping->getSourceFieldName()])) {
throw Doctrine_MappingException::duplicateFieldMapping();
}
$this->_associationMappings[$oneToOneMapping->getSourceFieldName()] = $oneToOneMapping;
}
/**
* @todo Implementation.
*/
public function oneToMany($targetEntity, $definition)
public function mapOneToMany(array $mapping)
{
}
......@@ -1675,7 +1466,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* @todo Implementation.
*/
public function manyToOne($targetEntity, $definition)
public function mapManyToOne(array $mapping)
{
}
......@@ -1683,7 +1474,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* @todo Implementation.
*/
public function manyToMany($targetEntity, $definition)
public function mapManyToMany(array $mapping)
{
}
......@@ -1696,7 +1487,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @param array $options
* @todo Unify under 'Behaviors'.
*/
public function actAs($tpl, array $options = array())
/*public function actAs($tpl, array $options = array())
{
if ( ! is_object($tpl)) {
if (class_exists($tpl, true)) {
......@@ -1723,7 +1514,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
$tpl->setTableDefinition();
return $this;
}
}*/
/**
* check
......@@ -1735,7 +1526,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
* @todo Should be done through $_tableOptions
* @deprecated
*/
public function check($constraint, $name = null)
/*public function check($constraint, $name = null)
{
if (is_array($constraint)) {
foreach ($constraint as $name => $def) {
......@@ -1754,7 +1545,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
} else {
$this->_tableOptions['checks'][] = $definition;
}
}
}*/
/**
* Registers a custom mapper for the entity class.
......@@ -1868,7 +1659,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
/**
* Adds a lifecycle callback for Entities of this class.
*
* Note: If a the same callback is registered more than once, the old one
* Note: If the same callback is registered more than once, the old one
* will be overridden.
*
* @param string $callback
......@@ -2016,6 +1807,14 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable
return $this->_parser->getRelations();
}
/**
* @todo Set by the factory if type is AUTO. Not pretty. Find sth. better.
*/
public function setIdGeneratorType($type)
{
$this->_generatorType = $type;
}
/**
*
*/
......
......@@ -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
......@@ -232,15 +234,6 @@ class Doctrine_ClassMetadata_Factory
}
} 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);
*/
}
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());
}
}
}
......
......@@ -33,10 +33,10 @@
* @since 1.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @todo Rename to EntityCollection
*/
class Doctrine_Collection extends Doctrine_Access implements Countable, IteratorAggregate, Serializable
{
protected $_entityBaseType;
/**
......@@ -44,42 +44,30 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*
* @var array
*/
protected $data = array();
/**
* The mapper object used to map the records of this collection to the database.
*
* @var Doctrine_Mapper
*/
protected $_mapper;
protected $_data = array();
/**
* A snapshot of the fetched data.
* A snapshot of the collection at the moment it was fetched from the database.
* This is used to create a diff of the collection at commit time.
*
* @var array
*/
protected $_snapshot = array();
/**
* This record this collection is attached to, if any.
* This entity that owns this collection.
*
* @var Doctrine_Entity
* @var Doctrine::ORM::Entity
*/
protected $_owner;
/**
* The reference field of the collection.
*
* @var string $referenceField
*/
protected $referenceField;
/**
* The relation this collection is related to, if any.
* The association mapping the collection belongs to.
* This is currently either a OneToManyMapping or a ManyToManyMapping.
*
* @var Doctrine_Relation
* @var Doctrine::ORM::Mapping::AssociationMapping
*/
protected $relation;
protected $_associationMapping;
/**
* The name of the column that is used for collection key mapping.
......@@ -101,6 +89,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
* @var EntityManager
*/
protected $_em;
protected $_isDirty = false;
/**
* Constructor.
......@@ -113,18 +102,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
{
$this->_entityBaseType = $entityBaseType;
$this->_em = Doctrine_EntityManagerFactory::getManager($entityBaseType);
$this->_mapper = $this->_em->getEntityPersister($entityBaseType);
if ($keyField === null) {
$keyField = $this->_mapper->getClassMetadata()->getBoundQueryPart('indexBy');
}
if ($keyField === null) {
//$keyField = $mapper->getClassMetadata()->getAttribute(Doctrine::ATTR_COLL_KEY);
}
if ($keyField !== null) {
if ( ! $this->_mapper->getClassMetadata()->hasField($keyField)) {
if ( ! $this->_em->getClassMetadata($entityBaseType)->hasField($keyField)) {
throw new Doctrine_Collection_Exception("Invalid field '$keyField' can't be uses as key.");
}
$this->_keyField = $keyField;
......@@ -139,7 +119,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function setData(array $data)
{
$this->data = $data;
$this->_data = $data;
}
/**
......@@ -155,14 +135,11 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$vars = get_object_vars($this);
unset($vars['reference']);
unset($vars['reference_field']);
unset($vars['relation']);
unset($vars['expandable']);
unset($vars['expanded']);
unset($vars['generator']);
$vars['_mapper'] = $vars['_mapper']->getComponentName();
return serialize($vars);
}
......@@ -187,12 +164,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$this->$name = $values;
}
$this->_mapper = $manager->getEntityPersister($this->_entityBaseType);
$keyColumn = isset($array['keyField']) ? $array['keyField'] : null;
if ($keyColumn === null) {
$keyColumn = $this->_mapper->getClassMetadata()->getBoundQueryPart('indexBy');
}
if ($keyColumn !== null) {
$this->_keyField = $keyColumn;
......@@ -231,7 +203,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getData()
{
return $this->data;
return $this->_data;
}
/**
......@@ -242,7 +214,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getFirst()
{
return reset($this->data);
return reset($this->_data);
}
/**
......@@ -253,7 +225,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getLast()
{
return end($this->data);
return end($this->_data);
}
/**
......@@ -263,7 +235,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function end()
{
return end($this->data);
return end($this->_data);
}
/**
......@@ -273,7 +245,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function key()
{
return key($this->data);
return key($this->_data);
}
/**
......@@ -285,13 +257,13 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
public function setReference(Doctrine_Entity $entity, Doctrine_Relation $relation)
{
$this->_owner = $entity;
$this->relation = $relation;
//$this->relation = $relation;
if ($relation instanceof Doctrine_Relation_ForeignKey ||
/*if ($relation instanceof Doctrine_Relation_ForeignKey ||
$relation instanceof Doctrine_Relation_LocalKey) {
$this->referenceField = $relation->getForeignFieldName();
$value = $entity->get($relation->getLocalFieldName());
foreach ($this->data as $entity) {
foreach ($this->_data as $entity) {
if ($value !== null) {
$entity->set($this->referenceField, $value, false);
} else {
......@@ -300,7 +272,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
}
} else if ($relation instanceof Doctrine_Relation_Association) {
}
}*/
}
/**
......@@ -322,8 +294,8 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function remove($key)
{
$removed = $this->data[$key];
unset($this->data[$key]);
$removed = $this->_data[$key];
unset($this->_data[$key]);
return $removed;
}
......@@ -336,7 +308,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function contains($key)
{
return isset($this->data[$key]);
return isset($this->_data[$key]);
}
/**
......@@ -344,7 +316,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function search(Doctrine_Entity $record)
{
return array_search($record, $this->data, true);
return array_search($record, $this->_data, true);
}
/**
......@@ -357,8 +329,8 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function get($key)
{
if (isset($this->data[$key])) {
return $this->data[$key];
if (isset($this->_data[$key])) {
return $this->_data[$key];
}
return null;
}
......@@ -374,7 +346,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$list = array();
$idFieldNames = (array)$this->_mapper->getClassMetadata()->getIdentifier();
foreach ($this->data as $record) {
foreach ($this->_data as $record) {
if (is_array($record)) {
if (count($idFieldNames) > 1) {
$id = array();
......@@ -406,7 +378,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getKeys()
{
return array_keys($this->data);
return array_keys($this->_data);
}
/**
......@@ -418,7 +390,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function count()
{
return count($this->data);
return count($this->_data);
}
/**
......@@ -429,69 +401,49 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
* @internal Can't type-hint the second parameter to Doctrine_Entity because we need
* to adhere to the Doctrine_Access::set() signature.
*/
public function set($key, $entity)
public function set($key, $value)
{
if ( ! $entity instanceof Doctrine_Entity) {
if ( ! $value instanceof Doctrine_Entity) {
throw new Doctrine_Collection_Exception('Value variable in set is not an instance of Doctrine_Entity');
}
if (isset($this->referenceField)) {
$entity->set($this->referenceField, $this->_owner, false);
}
$this->data[$key] = $entity;
$this->_data[$key] = $value;
}
/**
* adds a record to collection
*
* @param Doctrine_Entity $record record to be added
* @param string $key optional key for the record
* @return boolean
*/
public function add($record, $key = null)
public function add($value, $key = null)
{
/** @TODO Use raw getters/setters */
if ( ! $record instanceof Doctrine_Entity) {
if ( ! $value instanceof Doctrine_Entity) {
throw new Doctrine_Record_Exception('Value variable in set is not an instance of Doctrine_Entity.');
}
if (isset($this->referenceField)) {
$value = $this->_owner->get($this->relation->getLocalFieldName());
if ($value !== null) {
$record->set($this->referenceField, $value, false);
} else {
$record->set($this->referenceField, $this->_owner, false);
}
}
/*
* for some weird reason in_array cannot be used here (php bug ?)
*
* if used it results in fatal error : [ nesting level too deep ]
*/
foreach ($this->data as $val) {
if ($val === $record) {
foreach ($this->_data as $val) {
if ($val === $value) {
return false;
}
}
if (isset($key)) {
if (isset($this->data[$key])) {
if (isset($this->_data[$key])) {
return false;
}
$this->data[$key] = $record;
$this->_data[$key] = $value;
return true;
}
// why is this not checked when the keyColumn is set?
if (isset($this->_keyField)) {
$value = $record->get($this->_keyField);
if ($value === null) {
throw new Doctrine_Collection_Exception("Couldn't create collection index. Record field '".$this->_keyField."' was null.");
}
$this->data[$value] = $record;
} else {
$this->data[] = $record;
}
$this->_data[] = $value;
return true;
}
......@@ -509,7 +461,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$query = new Doctrine_Query($this->_mapper->getConnection());
if ( ! isset($name)) {
foreach ($this->data as $record) {
foreach ($this->_data as $record) {
// FIXME: composite key support
$ids = $record->identifier();
$value = count($ids) > 0 ? array_pop($ids) : null;
......@@ -528,11 +480,11 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$rel = $this->_mapper->getTable()->getRelation($name);
if ($rel instanceof Doctrine_Relation_LocalKey || $rel instanceof Doctrine_Relation_ForeignKey) {
foreach ($this->data as $record) {
foreach ($this->_data as $record) {
$list[] = $record[$rel->getLocal()];
}
} else {
foreach ($this->data as $record) {
foreach ($this->_data as $record) {
$ids = $record->identifier();
$value = count($ids) > 0 ? array_pop($ids) : null;
if ($value !== null) {
......@@ -563,15 +515,15 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$local = $rel->getLocal();
if ($rel instanceof Doctrine_Relation_LocalKey) {
foreach ($this->data as $key => $record) {
foreach ($this->_data as $key => $record) {
foreach ($coll as $k => $related) {
if ($related[$foreign] == $record[$local]) {
$this->data[$key]->_setRelated($name, $related);
$this->_data[$key]->_setRelated($name, $related);
}
}
}
} else if ($rel instanceof Doctrine_Relation_ForeignKey) {
foreach ($this->data as $key => $record) {
foreach ($this->_data as $key => $record) {
if ( ! $record->exists()) {
continue;
}
......@@ -584,7 +536,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
}
}
$this->data[$key]->_setRelated($name, $sub);
$this->_data[$key]->_setRelated($name, $sub);
}
} else if ($rel instanceof Doctrine_Relation_Association) {
// @TODO composite key support
......@@ -592,7 +544,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$asf = $rel->getAssociationFactory();
$name = $table->getComponentName();
foreach ($this->data as $key => $record) {
foreach ($this->_data as $key => $record) {
if ( ! $record->exists()) {
continue;
}
......@@ -603,7 +555,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
$sub->add($related->get($name));
}
}
$this->data[$key]->_setRelated($name, $sub);
$this->_data[$key]->_setRelated($name, $sub);
}
}
......@@ -635,7 +587,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function takeSnapshot()
{
$this->_snapshot = $this->data;
$this->_snapshot = $this->_data;
return $this;
}
......@@ -664,7 +616,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function processDiff()
{
foreach (array_udiff($this->_snapshot, $this->data, array($this, "_compareRecords")) as $record) {
foreach (array_udiff($this->_snapshot, $this->_data, array($this, "_compareRecords")) as $record) {
$record->delete();
}
return $this;
......@@ -687,6 +639,11 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
return $data;
}
public function isEmpty()
{
return $this->count() == 0;
}
/**
* fromArray
*
......@@ -776,7 +733,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getDeleteDiff()
{
return array_udiff($this->_snapshot, $this->data, array($this, "_compareRecords"));
return array_udiff($this->_snapshot, $this->_data, array($this, "_compareRecords"));
}
/**
......@@ -786,7 +743,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getInsertDiff()
{
return array_udiff($this->data, $this->_snapshot, array($this, "_compareRecords"));
return array_udiff($this->_data, $this->_snapshot, array($this, "_compareRecords"));
}
/**
......@@ -803,7 +760,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
}
/**
* save
* Saves all records of this collection and processes the
* difference of the last snapshot and the current data.
*
......@@ -869,7 +825,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
}
}
$this->data = array();
$this->_data = array();
if ($this->_owner) {
$this->_owner->free($deep);
......@@ -884,7 +840,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
*/
public function getIterator()
{
$data = $this->data;
$data = $this->_data;
return new ArrayIterator($data);
}
......@@ -907,6 +863,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
public function clear()
{
$this->data = array();
$this->_data = array();
}
}
......@@ -138,7 +138,8 @@ abstract class Doctrine_Connection implements Countable
* @var array $properties
*/
protected $properties = array(
'sql_comments' => array(array('start' => '--', 'end' => "\n", '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,
......@@ -154,6 +155,8 @@ abstract class Doctrine_Connection implements Countable
/**
* The parameters used during creation of the Connection.
*
* @var array
*/
protected $_params = array();
......@@ -364,7 +367,10 @@ 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')) {
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']) ?
......@@ -379,9 +385,6 @@ abstract class Doctrine_Connection implements Countable
);
$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->_pdo->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
/*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,11 +795,10 @@ 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) { }
} catch (PDOException $e) {
$this->rethrowException($e, $this);
}
}
/**
* queries the database with limit and offset
......@@ -859,11 +859,10 @@ abstract class Doctrine_Connection implements Countable
return $stmt;
}
} catch (Doctrine_Adapter_Exception $e) {
} catch (PDOException $e) { }
} catch (PDOException $e) {
$this->rethrowException($e, $this);
}
}
/**
* exec
......@@ -894,11 +893,10 @@ abstract class Doctrine_Connection implements Countable
return $count;
}
} catch (Doctrine_Adapter_Exception $e) {
} catch (PDOException $e) { }
} 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;
}
//$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()
{
}
}
......@@ -21,39 +21,23 @@
#namespace Doctrine::ORM::Internal;
#use Doctrine::ORM::Entity;
#use Doctrine::ORM::EntityManager;
#use Doctrine::ORM::Exceptions::UnitOfWorkException;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
* "object-level" transaction and for writing out changes to the database at
* "object-level" transaction and for writing out changes to the database
* in the correct order.
*
* Some terminology:
*
* <b>New entity</b>: A new entity is an entity that already has an identity but
* is not yet persisted into the database. This is usually the case for all
* newly saved/persisted entities that use a SEQUENCE id generator. Entities with an
* IDENTITY id generator get persisted as soon as they're saved in order to
* obtain the identifier. Therefore entities that use an IDENTITY id generator
* never appear in the list of new entities of the UoW.
* New entities are inserted into the database when the is UnitOfWork committed.
*
* <b>Dirty entity</b>: A dirty entity is a managed entity whose values have
* been altered.
*
* <b>Removed entity</b>: A removed entity is a managed entity that is scheduled
* for deletion from the database.
*
* <b>Clean entity</b>: A clean entity is a managed entity that has been fetched
* from the database and whose values have not yet been altered.
*
* @package Doctrine
* @subpackage Connection
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 2.0
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @todo package:orm. Figure out a useful implementation.
* @todo Rename: Doctrine::ORM::(Internal::)UnitOfWork.
* @todo Turn connection exceptions into UnitOfWorkExceptions.
*/
class Doctrine_Connection_UnitOfWork
{
......@@ -68,22 +52,36 @@ class Doctrine_Connection_UnitOfWork
protected $_identityMap = array();
/**
* A list of all new entities.
* A list of all new entities that need to be INSERTed.
*
* @var array
* @todo Index by class name.
* @todo Rename to _inserts?
*/
protected $_newEntities = array();
/**
* A list of all dirty entities.
*
* @var array
* @todo Rename to _updates?
*/
protected $_dirtyEntities = array();
/**
* A list of all removed entities.
* A list of all deleted entities.
* Removed entities are entities that are "scheduled for removal" but have
* not yet been removed from the database.
*
* @var array
* @todo Rename to _deletions?
*/
protected $_removedEntities = array();
protected $_deletedEntities = array();
/**
* The EntityManager the UnitOfWork belongs to.
*
* @var Doctrine::ORM::EntityManager
*/
protected $_em;
......@@ -91,7 +89,7 @@ class Doctrine_Connection_UnitOfWork
* The calculator used to calculate the order in which changes to
* entities need to be written to the database.
*
* @var unknown_type
* @var Doctrine::ORM::Internal::CommitOrderCalculator
* @todo Implementation. Replace buildFlushTree().
*/
protected $_commitOrderCalculator;
......@@ -105,6 +103,8 @@ class Doctrine_Connection_UnitOfWork
public function __construct(Doctrine_EntityManager $em)
{
$this->_em = $em;
//TODO: any benefit with lazy init?
$this->_commitOrderCalculator = new Doctrine_Internal_CommitOrderCalculator();
}
/**
......@@ -115,42 +115,178 @@ class Doctrine_Connection_UnitOfWork
*/
public function commit()
{
$this->_orderCommits();
// Detect changes in managed entities (mark dirty)
//TODO: Consider using registerDirty() in Entity#set() instead if its
// more performant.
foreach ($this->_identityMap as $entities) {
foreach ($entities as $entity) {
if ($entity->_state() == Doctrine_Entity::STATE_MANAGED
&& $entity->isModified()) {
$this->registerDirty($entity);
}
}
}
if (empty($this->_newEntities) &&
empty($this->_deletedEntities) &&
empty($this->_dirtyEntities)) {
return; // Nothing to do.
}
// Now we need a commit order to maintain referential integrity
$commitOrder = $this->_getCommitOrder();
$this->_insertNew();
$this->_updateDirty();
$this->_deleteRemoved();
//TODO: begin transaction here?
foreach ($commitOrder as $class) {
$this->_executeInserts($class);
$this->_executeUpdates($class);
}
private function _orderCommits()
// Deletions come last and need to be in reverse commit order
for ($count = count($commitOrder), $i = $count - 1; $i >= 0; $i--) {
$this->_executeDeletions($commitOrder[$i]);
}
//TODO: commit transaction here?
// clear lists
$this->_newEntities = array();
$this->_dirtyEntities = array();
$this->_deletedEntities = array();
}
private function _executeInserts($class)
{
//TODO: Maybe $persister->addInsert($entity) in the loop and
// $persister->executeInserts() at the end to allow easy prepared
// statement reuse and maybe bulk operations in the persister.
// Same for update/delete.
$className = $class->getClassName();
$persister = $this->_em->getEntityPersister($className);
foreach ($this->_newEntities as $entity) {
if ($entity->getClass()->getClassName() == $className) {
$persister->insert($entity);
}
}
}
private function _executeUpdates($class)
{
$className = $class->getClassName();
$persister = $this->_em->getEntityPersister($className);
foreach ($this->_dirtyEntities as $entity) {
if ($entity->getClass()->getClassName() == $className) {
$persister->update($entity);
}
}
}
private function _executeDeletions($class)
{
$className = $class->getClassName();
$persister = $this->_em->getEntityPersister($className);
foreach ($this->_deletedEntities as $entity) {
if ($entity->getClass()->getClassName() == $className) {
$persister->delete($entity);
}
}
}
/**
* Register a new entity.
* Gets the commit order.
*
* @return array
*/
public function registerNew(Doctrine_Entity $entity)
private function _getCommitOrder()
{
if ( ! $entity->identifier()) {
throw new Doctrine_Connection_Exception("Entity without identity "
. "can't be registered as new.");
//TODO: Once these 3 arrays are indexed by classname we can do this:
// Either way... do we need to care about duplicates?
/*$classesInChangeSet = array_merge(
array_keys($this->_newEntities),
array_keys($this->_dirtyEntities),
array_keys($this->_deletedEntities)
);*/
$entityChangeSet = array_merge($this->_newEntities, $this->_dirtyEntities, $this->_deletedEntities);
/* if (count($entityChangeSet) == 1) {
* return array($entityChangeSet[0]->getClass());
* }
*/
// See if there are any new classes in the changeset, that are not in the
// commit order graph yet (dont have a node).
$newNodes = array();
foreach ($entityChangeSet as $entity) {
if ( ! $this->_commitOrderCalculator->hasNodeWithKey($entity->getClass()->getClassName())) {
$this->_commitOrderCalculator->addNodeWithItem(
$entity->getClass()->getClassName(), // index/key
$entity->getClass() // item
);
$newNodes[] = $this->_commitOrderCalculator->getNodeForKey($entity->getClass()->getClassName());
}
}
// Calculate dependencies for new nodes
foreach ($newNodes as $node) {
foreach ($node->getClass()->getAssociationMappings() as $assocMapping) {
//TODO: should skip target classes that are not in the changeset.
if ($assocMapping->isOwningSide()) {
$targetClass = $assocMapping->getTargetClass();
$targetClassName = $targetClass->getClassName();
// if the target class does not yet have a node, create it
if ( ! $this->_commitOrderCalculator->hasNodeWithKey($targetClassName)) {
$this->_commitOrderCalculator->addNodeWithItem(
$targetClassName, // index/key
$targetClass // item
);
}
// add dependency
$otherNode = $this->_commitOrderCalculator->getNodeForKey($targetClassName);
$node->before($otherNode);
}
}
}
return $this->_commitOrderCalculator->getCommitOrder();
}
/**
* Register a new entity.
*
* @todo Rename to scheduleForInsert().
*/
public function registerNew(Doctrine_Entity $entity)
{
$oid = $entity->getOid();
/*if ( ! $entity->_identifier()) {
throw new Doctrine_Connection_Exception("Entity without identity cant be registered as new.");
}*/
if (isset($this->_dirtyEntities[$oid])) {
throw new Doctrine_Connection_Exception("Dirty object can't be registered as new.");
} else if (isset($this->_removedEntities[$oid])) {
}
if (isset($this->_deletedEntities[$oid])) {
throw new Doctrine_Connection_Exception("Removed object can't be registered as new.");
} else if (isset($this->_newEntities[$oid])) {
}
if (isset($this->_newEntities[$oid])) {
throw new Doctrine_Connection_Exception("Object already registered as new. Can't register twice.");
}
$this->registerIdentity($entity);
$this->_newEntities[$oid] = $entity;
if ($entity->_identifier()) {
$this->addToIdentityMap($entity);
}
}
/**
* Checks whether an entity is registered as new on the unit of work.
*
* @param Doctrine_Entity $entity
* @return boolean
* @todo Rename to isScheduledForInsert().
*/
public function isRegisteredNew(Doctrine_Entity $entity)
{
return isset($this->_newEntities[$entity->getOid()]);
......@@ -158,30 +294,46 @@ class Doctrine_Connection_UnitOfWork
/**
* Registers a clean entity.
* The entity is simply put into the identity map.
*
* @param Doctrine::ORM::Entity $entity
*/
public function registerClean(Doctrine_Entity $entity)
{
$this->registerIdentity($entity);
$this->addToIdentityMap($entity);
}
/**
* Registers a dirty entity.
*
* @param Doctrine::ORM::Entity $entity
* @todo Rename to scheduleForUpdate().
*/
public function registerDirty(Doctrine_Entity $entity)
{
if ( ! $entity->identifier()) {
$oid = $entity->getOid();
if ( ! $entity->_identifier()) {
throw new Doctrine_Connection_Exception("Entity without identity "
. "can't be registered as dirty.");
}
$oid = $entity->getOid();
if (isset($this->_removedEntities[$entity->getOid()])) {
if (isset($this->_deletedEntities[$oid])) {
throw new Doctrine_Connection_Exception("Removed object can't be registered as dirty.");
}
if ( ! isset($this->_dirtyEntities[$oid], $this->_newEntities[$oid])) {
$this->_dirtyEntities[$entity->getOid()] = $entity;
if ( ! isset($this->_dirtyEntities[$oid]) && ! isset($this->_newEntities[$oid])) {
$this->_dirtyEntities[$oid] = $entity;
}
}
/**
* Checks whether an entity is registered as dirty in the unit of work.
* Note: Is not very useful currently as dirty entities are only registered
* at commit time.
*
* @param Doctrine_Entity $entity
* @return boolean
* @todo Rename to isScheduledForUpdate().
*/
public function isRegisteredDirty(Doctrine_Entity $entity)
{
return isset($this->_dirtyEntities[$entity->getOid()]);
......@@ -189,260 +341,137 @@ class Doctrine_Connection_UnitOfWork
/**
* Registers a deleted entity.
*
* @todo Rename to scheduleForDelete().
*/
public function registerRemoved(Doctrine_Entity $entity)
public function registerDeleted(Doctrine_Entity $entity)
{
if ($entity->isNew()) {
$oid = $entity->getOid();
if ( ! $this->isInIdentityMap($entity)) {
return;
}
$this->unregisterIdentity($entity);
$oid = $entity->getOid();
$this->removeFromIdentityMap($entity);
if (isset($this->_newEntities[$oid])) {
unset($this->_newEntities[$oid]);
return;
return; // entity has not been persisted yet, so nothing more to do.
}
/* Seems unnecessary since _dirtyEntities is filled & cleared on commit, not earlier
if (isset($this->_dirtyEntities[$oid])) {
unset($this->_dirtyEntities[$oid]);
}*/
if ( ! isset($this->_deletedEntities[$oid])) {
$this->_deletedEntities[$oid] = $entity;
}
if ( ! isset($this->_removedEntities[$oid])) {
$this->_removedEntities[$oid] = $entity;
}
}
public function isRegisteredRemoved(Doctrine_Entity $entity)
{
return isset($this->_removedEntities[$entity->getOid()]);
}
/**
* builds a flush tree that is used in transactions
* Checks whether an entity is registered as removed/deleted with the unit
* of work.
*
* The returned array has all the initialized components in
* 'correct' order. Basically this means that the records of those
* components can be saved safely in the order specified by the returned array.
*
* @param array $tables an array of Doctrine_Table objects or component names
* @return array an array of component names in flushing order
* @param Doctrine::ORM::Entity $entity
* @return boolean
* @todo Rename to isScheduledForDelete().
*/
public function buildFlushTree(array $entityNames)
public function isRegisteredRemoved(Doctrine_Entity $entity)
{
$tree = array();
foreach ($entityNames as $k => $entity) {
if ( ! ($mapper instanceof Doctrine_Mapper)) {
$mapper = $this->conn->getMapper($mapper);
}
$nm = $mapper->getComponentName();
$index = array_search($nm, $tree);
if ($index === false) {
$tree[] = $nm;
$index = max(array_keys($tree));
}
$rels = $mapper->getClassMetadata()->getRelations();
// group relations
foreach ($rels as $key => $rel) {
if ($rel instanceof Doctrine_Relation_ForeignKey) {
unset($rels[$key]);
array_unshift($rels, $rel);
}
}
foreach ($rels as $rel) {
$name = $rel->getTable()->getComponentName();
$index2 = array_search($name, $tree);
$type = $rel->getType();
// skip self-referenced relations
if ($name === $nm) {
continue;
}
if ($rel instanceof Doctrine_Relation_ForeignKey) {
if ($index2 !== false) {
if ($index2 >= $index)
continue;
unset($tree[$index]);
array_splice($tree,$index2,0,$nm);
$index = $index2;
} else {
$tree[] = $name;
}
} else if ($rel instanceof Doctrine_Relation_LocalKey) {
if ($index2 !== false) {
if ($index2 <= $index)
continue;
unset($tree[$index2]);
array_splice($tree, $index, 0, $name);
} else {
array_unshift($tree,$name);
$index++;
}
} else if ($rel instanceof Doctrine_Relation_Association) {
$t = $rel->getAssociationFactory();
$n = $t->getComponentName();
if ($index2 !== false) {
unset($tree[$index2]);
}
array_splice($tree, $index, 0, $name);
$index++;
$index3 = array_search($n, $tree);
if ($index3 !== false) {
if ($index3 >= $index)
continue;
unset($tree[$index]);
array_splice($tree, $index3, 0, $n);
$index = $index2;
} else {
$tree[] = $n;
}
}
}
}
return $tree;
return isset($this->_deletedEntities[$entity->getOid()]);
}
/**
* persists all the pending records from all tables
* Detaches an entity from the persistence management. It's persistence will
* no longer be managed by Doctrine.
*
* @throws PDOException if something went wrong at database level
* @return void
* @deprecated
*/
/*public function saveAll()
{
$this->conn->beginInternalTransaction();
// get the flush tree
$tree = $this->buildFlushTree($this->conn->getMappers());
$tree = array_combine($tree, array_fill(0, count($tree), array()));
foreach ($this->_managedEntities as $oid => $entity) {
$className = $entity->getClassName();
$tree[$className][] = $entity;
}
// save all records
foreach ($tree as $className => $entities) {
$mapper = $this->conn->getMapper($className);
foreach ($entities as $entity) {
$mapper->saveSingleRecord($entity);
}
}
// save all associations
foreach ($tree as $className => $entities) {
$mapper = $this->conn->getMapper($className);
foreach ($entities as $entity) {
$mapper->saveAssociations($entity);
}
}
$this->conn->commit();
}*/
/**
* Adds an entity to the pool of managed entities.
* @deprecated
* @param integer $oid object identifier
* @return boolean whether ot not the operation was successful
*/
public function manage(Doctrine_Entity $entity)
public function detach(Doctrine_Entity $entity)
{
$oid = $entity->getOid();
if ( ! isset($this->_managedEntities[$oid])) {
$this->_managedEntities[$oid] = $entity;
return true;
if ($this->isInIdentityMap($entity)) {
$this->removeFromIdentityMap($entity);
}
return false;
}
/**
* @param integer $oid object identifier
* @return boolean whether ot not the operation was successful
* @deprecated The new implementation of detach() should remove the entity
* from the identity map.
* Enter description here...
*
* @param Doctrine_Entity $entity
* @return unknown
* @todo Rename to isScheduled()
*/
public function detach(Doctrine_Entity $entity)
public function isEntityRegistered(Doctrine_Entity $entity)
{
$oid = $entity->getOid();
if ( ! isset($this->_managedEntities[$oid])) {
return false;
}
unset($this->_managedEntities[$oid]);
return true;
return isset($this->_newEntities[$oid]) ||
//isset($this->_dirtyEntities[$oid]) ||
isset($this->_deletedEntities[$oid]) ||
$this->isInIdentityMap($entity);
}
/**
* Detaches all currently managed entities.
* Alternatively, if an entity class name is given, all entities of that type
* (or subtypes) are detached. Don't forget that entities are registered in
* the identity map with the name of the root entity class. So calling detachAll()
* with a class name that is not the name of a root entity has no effect.
*
* @return integer The number of detached entities.
* @todo Deprecated. The new implementation should remove all entities from
* the identity map.
*/
public function detachAll()
public function detachAll($entityName = null)
{
$numDetached = count($this->_managedEntities);
$this->_managedEntities = array();
//TODO: what do do with new/dirty/removed lists?
$numDetached = 0;
if ($entityName !== null && isset($this->_identityMap[$entityName])) {
$numDetached = count($this->_identityMap[$entityName]);
$this->_identityMap[$entityName] = array();
} else {
$numDetached = count($this->_identityMap);
$this->_identityMap = array();
}
return $numDetached;
}
/**
* Registers an entity in the identity map.
* Note that entities in a hierarchy are registered with the class name of
* the root entity.
*
* @param Doctrine::ORM::Entity $entity The entity to register.
* @return boolean TRUE if the registration was successful, FALSE if the identity of
* the entity in question is already managed.
* @throws Doctrine_Connection_Exception If the entity has no (database) identity.
*/
public function registerIdentity(Doctrine_Entity $entity)
public function addToIdentityMap(Doctrine_Entity $entity)
{
$idHash = $this->getIdentifierHash($entity->identifier());
if ( ! $idHash) {
$idHash = $this->getIdentifierHash($entity->_identifier());
if ($idHash === '') {
throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid()
. "' has no identity and therefore can't be added to the identity map.");
}
$className = $entity->getClassMetadata()->getRootClassName();
$className = $entity->getClass()->getRootClassName();
if (isset($this->_identityMap[$className][$idHash])) {
return false;
}
$this->_identityMap[$className][$idHash] = $entity;
$entity->_state(Doctrine_Entity::STATE_MANAGED);
return true;
}
/**
* Enter description here...
*
* @param unknown_type $entityName
* @todo unify with detachAll()
*/
public function clearIdentitiesForEntity($entityName)
{
$this->_identityMap[$entityName] = array();
}
/**
* Removes an entity from the identity map.
*
* @param Doctrine_Entity $entity
* @return unknown
* @todo This will be the new detach().
*/
public function unregisterIdentity(Doctrine_Entity $entity)
public function removeFromIdentityMap(Doctrine_Entity $entity)
{
$idHash = $this->getIdentifierHash($entity->identifier());
if ( ! $idHash) {
$idHash = $this->getIdentifierHash($entity->_identifier());
if ($idHash === '') {
throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid()
. "' has no identity and therefore can't be removed from the identity map.");
}
$className = $entity->getClassMetadata()->getRootClassName();
$className = $entity->getClass()->getRootClassName();
if (isset($this->_identityMap[$className][$idHash])) {
unset($this->_identityMap[$className][$idHash]);
return true;
......@@ -454,15 +483,14 @@ class Doctrine_Connection_UnitOfWork
/**
* Finds an entity in the identity map by its identifier hash.
*
* @param unknown_type $idHash
* @param unknown_type $rootClassName
* @return unknown
* @param string $idHash
* @param string $rootClassName
* @return Doctrine::ORM::Entity
*/
public function getByIdHash($idHash, $rootClassName)
{
return $this->_identityMap[$rootClassName][$idHash];
}
public function tryGetByIdHash($idHash, $rootClassName)
{
if ($this->containsIdHash($idHash, $rootClassName)) {
......@@ -473,9 +501,14 @@ class Doctrine_Connection_UnitOfWork
/**
* Gets the identifier hash for a set of identifier values.
* The hash is just a concatenation of the identifier values.
* The identifiers are concatenated with a space.
*
* Note that this method always returns a string. If the given array is
* empty, an empty string is returned.
*
* @param array $id
* @return string
* @return string The hash.
*/
public function getIdentifierHash(array $id)
{
......@@ -483,21 +516,23 @@ class Doctrine_Connection_UnitOfWork
}
/**
* Checks whether an entity is registered in the identity map.
* Checks whether an entity is registered in the identity map of the
* UnitOfWork.
*
* @param Doctrine_Entity $entity
* @return boolean
*/
public function contains(Doctrine_Entity $entity)
public function isInIdentityMap(Doctrine_Entity $entity)
{
$idHash = $this->getIdentifierHash($entity->identifier());
if ( ! $idHash) {
$idHash = $this->getIdentifierHash($entity->_identifier());
if ($idHash === '') {
return false;
}
return isset($this->_identityMap
[$entity->getClassMetadata()->getRootClassName()]
[$idHash]);
[$entity->getClass()->getRootClassName()]
[$idHash]
);
}
/**
......@@ -512,35 +547,145 @@ class Doctrine_Connection_UnitOfWork
return isset($this->_identityMap[$rootClassName][$idHash]);
}
/**
* Saves an entity as part of the current unit of work.
*
* @param Doctrine_Entity $entity The entity to save.
*/
public function save(Doctrine_Entity $entity)
{
$insertNow = array();
$visited = array();
$this->_doSave($entity, $visited, $insertNow);
if ( ! empty($insertNow)) {
// We have no choice. This means that there are either new entities
// with an IDENTITY key generation or with a natural identifier.
// In both cases we must commit the inserts instantly.
//TODO: Isnt it enough to only execute the inserts instead of full flush?
$this->commit();
}
}
/**
* Saves an entity as part of the current unit of work.
* This method is internally called during save() cascades as it tracks
* the already visited entities to prevent infinite recursions.
*
* @param Doctrine_Entity $entity The entity to save.
* @param array $visited The already visited entities.
*/
private function _doSave(Doctrine_Entity $entity, array &$visited, array &$insertNow)
{
if (isset($visited[$entity->getOid()])) {
return; // Prevent infinite recursion
}
$visited[$entity->getOid()] = $entity; // mark visited
$class = $entity->getClass();
switch ($entity->_state()) {
case Doctrine_Entity::STATE_CLEAN:
//nothing to do
// ignore $entity but cascade
case Doctrine_Entity::STATE_MANAGED:
// nothing to do for $entity
break;
case Doctrine_Entity::STATE_DIRTY:
// update
$this->registerDirty($entity);
// todo:cascade
case Doctrine_Entity::STATE_NEW:
if ($class->isIdGeneratorIdentity()) {
$insertNow[$entity->getOid()] = $entity;
$this->_newEntities[$entity->getOid()] = $entity;
} else if ( ! $class->usesIdGenerator()) {
$insertNow[$entity->getOid()] = $entity;
//...
} else if ($class->isIdGeneratorSequence()) {
// Get the next sequence number
//TODO: sequence name?
$id = $this->_em->getConnection()->getSequenceModule()->nextId("foo");
$entity->set($class->getSingleIdentifierFieldName(), $id);
$this->registerNew($entity);
} else {
throw new Doctrine_Exception("Unable to handle ID generation of new entity.");
}
break;
case Doctrine_Entity::STATE_TCLEAN:
case Doctrine_Entity::STATE_TDIRTY:
// insert
// if identifier type IDENTITY:
// cascade
// if no transaction is started yet, do it
// force insert (directly to persister)
// else
// cascade
// get & assign the identifier, then registerNew()
case Doctrine_Entity::STATE_DETACHED:
//exception?
throw new Doctrine_Exception("Behavior of save() for a detached entity "
. "is not yet defined.");
case Doctrine_Entity::STATE_DELETED:
// $entity becomes managed again
if ($this->isRegisteredRemoved($entity)) {
//TODO: better a method for this?
unset($this->_deletedEntities[$entity->getOid()]);
} else {
//FIXME: There's more to think of here...
$this->registerNew($entity);
}
break;
default:
//TODO: throw UnitOfWorkException::invalidEntityState()
throw new Doctrine_Exception("Encountered invalid entity state.");
}
$this->_cascadeSave($entity, $visited, $insertNow);
}
/**
* Deletes an entity as part of the current unit of work.
*
* @param Doctrine_Entity $entity
*/
public function delete(Doctrine_Entity $entity)
{
$this->_doDelete($entity, array());
}
private function _cascadeSave(Doctrine_Entity $entity)
private function _doDelete(Doctrine_Entity $entity, array &$visited)
{
if (isset($visited[$entity->getOid()])) {
return; // Prevent infinite recursion
}
$visited[$entity->getOid()] = $entity; // mark visited
$class = $entity->getClass();
switch ($entity->_state()) {
case Doctrine_Entity::STATE_NEW:
case Doctrine_Entity::STATE_DELETED:
// nothing to do for $entity
break;
case Doctrine_Entity::STATE_MANAGED:
$this->registerDeleted($entity);
break;
case Doctrine_Entity::STATE_DETACHED:
//exception?
throw new Doctrine_Exception("A detached entity can't be deleted.");
default:
//TODO: throw UnitOfWorkException::invalidEntityState()
throw new Doctrine_Exception("Encountered invalid entity state.");
}
$this->_cascadeDelete($entity, $visited);
}
/**
* Cascades the save operation to associated entities.
*
* @param Doctrine_Entity $entity
* @param array $visited
*/
private function _cascadeSave(Doctrine_Entity $entity, array &$visited, array &$insertNow)
{
foreach ($entity->getClass()->getAssociationMappings() as $assocMapping) {
if ( ! $assocMapping->isCascadeSave()) {
continue;
}
$relatedEntities = $entity->get($assocMapping->getSourceFieldName());
if ($relatedEntities instanceof Doctrine_Entity) {
$this->_doSave($relatedEntities, $visited, $insertNow);
} else if ($relatedEntities instanceof Doctrine_Collection &&
count($relatedEntities) > 0) {
foreach ($relatedEntities as $relatedEntity) {
$this->_doSave($relatedEntity, $visited, $insertNow);
}
}
}
}
private function _cascadeDelete(Doctrine_Entity $entity)
......@@ -548,6 +693,17 @@ class Doctrine_Connection_UnitOfWork
}
public function getCommitOrderCalculator()
{
return $this->_commitOrderCalculator;
}
public function close()
{
//...
$this->_commitOrderCalculator->clear();
}
// Stuff from 0.11/1.0 that we will need later (need to modify it though)
......@@ -733,6 +889,11 @@ class Doctrine_Connection_UnitOfWork
return $fullCondition;
}*/
public function getIdentityMap()
{
return $this->_identityMap;
}
}
......
......@@ -37,45 +37,23 @@
* @link www.phpdoctrine.org
* @since 2.0
* @version $Revision: 4342 $
* @todo Split up into "Entity" and "ActiveEntity" (extends Entity)
* @todo Move entity states into a separate enumeration (EntityStates).
* They do not need to be exposed to users in such a way. The states are mainly
* for internal use.
* @todo Split up into "Entity" and "ActiveEntity" (extends Entity).
*/
abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
{
/**
* DIRTY STATE
* An Entity is in dirty state when its properties are changed.
* MANAGED
* An Entity is in managed state when it has a primary key/identifier and is
* managed by an EntityManager (registered in the identity map).
*/
const STATE_DIRTY = 1;
const STATE_MANAGED_DIRTY = 1;
const STATE_MANAGED = 1;
/**
* TDIRTY STATE
* An Entity is in transient dirty state when it is created and some of its
* fields are modified but it is NOT yet persisted into database.
* NEW
* An Entity is new if it does not yet have an identifier/primary key
* and is not (yet) managed by an EntityManager.
*/
const STATE_TDIRTY = 2;
const STATE_NEW_DIRTY = 2;
/**
* CLEAN STATE
* An Entity is in clean state when all of its properties are loaded from the database
* and none of its properties are changed.
*/
const STATE_CLEAN = 3;
const STATE_MANAGED_CLEAN = 3;
/**
* NEW TCLEAN
* An Entity is in transient clean state when it is created and none of its
* fields are modified.
* @todo Do we need this state? Just STATE_NEW may be enough without differentiating
* clean/dirty. A new entity is always "dirty".
*/
const STATE_TCLEAN = 5;
const STATE_NEW_CLEAN = 5;
const STATE_NEW = 2;
/**
* LOCKED STATE
......@@ -93,14 +71,14 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
* (or no longer) associated with an EntityManager (and a UnitOfWork).
* This means its no longer in the identity map.
*/
const STATE_DETACHED = 7;
const STATE_DETACHED = 3;
/**
* A removed Entity instance is an instance with a persistent identity,
* associated with an EntityManager, that is scheduled for removal from the
* database.
*/
const STATE_DELETED = 8;
const STATE_DELETED = 4;
/**
* Index used for creating object identifiers (oid's).
......@@ -144,12 +122,6 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
*/
private $_entityName;
/**
* @var Doctrine_Node_<TreeImpl> node object
* @todo Specific to the NestedSet Behavior plugin. Move outta here.
*/
//protected $_node;
/**
* The values that make up the ID/primary key of the entity.
*
......@@ -168,15 +140,15 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
* The state of the object.
*
* @var integer
* @see STATE_* constants
*/
private $_state;
/**
* The names of fields that have been modified but not yet persisted.
* Keys are field names, values oldValue => newValue tuples.
*
* @var array
* @todo Better name? $_modifiedFields?
* @todo Rename to $_changeSet
*/
private $_modified = array();
......@@ -204,6 +176,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
/**
* Constructor.
* Creates a new Entity instance.
*/
public function __construct()
{
......@@ -214,9 +187,9 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
$this->_data = $this->_em->_getTmpEntityData();
if ($this->_data) {
$this->_extractIdentifier();
$this->_state = self::STATE_CLEAN;
$this->_state = self::STATE_MANAGED;
} else {
$this->_state = self::STATE_TCLEAN;
$this->_state = self::STATE_NEW;
}
// @todo read from attribute the first time and move this initialization elsewhere.
......@@ -265,7 +238,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
}*/
/**
* hydrates this object from given array
* Hydrates this object from given array
*
* @param array $data
* @return boolean
......@@ -278,24 +251,18 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
/**
* Copies the identifier names and values from _data into _id.
*
* @param boolean $exists whether or not this record exists in persistent data store
* @return void
* @todo Looks like its better placed elsewhere (EntityManager?)
*/
private function _extractIdentifier()
{
switch ($this->_class->getIdentifierType()) {
case Doctrine::IDENTIFIER_AUTOINC:
case Doctrine::IDENTIFIER_SEQUENCE:
case Doctrine::IDENTIFIER_NATURAL:
if ( ! $this->_class->isIdentifierComposite()) {
// Single field identifier
$name = $this->_class->getIdentifier();
$name = $name[0];
if (isset($this->_data[$name]) && $this->_data[$name] !== Doctrine_Null::$INSTANCE) {
$this->_id[$name] = $this->_data[$name];
}
break;
case Doctrine::IDENTIFIER_COMPOSITE:
} else {
// Composite identifier
$names = $this->_class->getIdentifier();
foreach ($names as $name) {
if ($this->_data[$name] === Doctrine_Null::$INSTANCE) {
......@@ -304,7 +271,6 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
$this->_id[$name] = $this->_data[$name];
}
}
break;
}
}
......@@ -438,22 +404,16 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
/* TODO: Do we really need this check? This is only for internal use after all. */
switch ($state) {
case self::STATE_TCLEAN:
case self::STATE_CLEAN:
case self::STATE_TDIRTY:
case self::STATE_DIRTY:
case self::STATE_PROXY:
case self::STATE_MANAGED:
case self::STATE_DELETED:
case self::STATE_DETACHED:
case self::STATE_NEW:
case self::STATE_LOCKED:
$this->_state = $state;
break;
default:
throw Doctrine_Entity_Exception::invalidState($state);
}
if ($this->_state === Doctrine_Entity::STATE_TCLEAN ||
$this->_state === Doctrine_Entity::STATE_CLEAN) {
$this->_modified = array();
}
}
/**
......@@ -878,23 +838,13 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
}*/
$old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null;
//FIXME: null == 0 => true
if ($old != $value) {
$this->_data[$fieldName] = $value;
$this->_modified[$fieldName] = array($old => $value);
if ($this->isNew() && $this->_class->isIdentifier($fieldName)) {
$this->_id[$fieldName] = $value;
}
switch ($this->_state) {
case Doctrine_Entity::STATE_CLEAN:
$this->_state = Doctrine_Entity::STATE_DIRTY;
break;
case Doctrine_Entity::STATE_TCLEAN:
$this->_state = Doctrine_Entity::STATE_TDIRTY;
break;
}
}
} else if ($this->_class->hasRelation($fieldName)) {
$this->_rawSetReference($fieldName, $value);
......@@ -1253,7 +1203,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
*/
final public function isNew()
{
return $this->_state == self::STATE_TCLEAN || $this->_state == self::STATE_TDIRTY;
return $this->_state == self::STATE_NEW;
}
/**
......@@ -1264,8 +1214,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
*/
final public function isModified()
{
return ($this->_state === Doctrine_Entity::STATE_DIRTY ||
$this->_state === Doctrine_Entity::STATE_TDIRTY);
return count($this->_modified) > 0;
}
/**
......@@ -1349,44 +1298,33 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
/**
* INTERNAL:
* Assigns an identifier to the entity. This is only intended for use by
* the EntityPersisters or the UnitOfWork.
*
* @param integer $id
* @return void
* @todo Not sure this is the right place here.
* @param mixed $id
*/
final public function assignIdentifier($id = false)
final public function _assignIdentifier($id)
{
if ($id === false) {
$this->_id = array();
$this->_state = Doctrine_Entity::STATE_TCLEAN;
$this->_modified = array();
} else if ($id === true) {
$this->_extractIdentifier(true);
$this->_state = Doctrine_Entity::STATE_CLEAN;
$this->_modified = array();
} else {
if (is_array($id)) {
foreach ($id as $fieldName => $value) {
$this->_id[$fieldName] = $value;
$this->_data[$fieldName] = $value;
}
} else {
$idFieldNames = $this->_class->getIdentifier();
$name = $idFieldNames[0];
$name = $this->_class->getSingleIdentifierFieldName();
$this->_id[$name] = $id;
$this->_data[$name] = $id;
}
$this->_state = self::STATE_CLEAN;
$this->_modified = array();
}
}
/**
* returns the primary keys of this object
* INTERNAL:
* Returns the primary keys of the entity (key => value pairs).
*
* @return array
*/
final public function identifier()
final public function _identifier()
{
return $this->_id;
}
......@@ -1662,7 +1600,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable
*
* @return Doctrine::ORM::Mapping::ClassMetadata
*/
final public function getClassMetadata()
final public function getClass()
{
return $this->_class;
}
......
......@@ -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
{
......
......@@ -32,6 +32,7 @@
* @version $Revision$
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @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(
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 50,
'accessor' => 'getUsernameCustom',
'mutator' => 'setUsernameCustom'));
'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
<?php
require_once 'lib/mocks/Doctrine_EntityPersisterMock.php';
class Doctrine_EntityManagerMock extends Doctrine_EntityManager
{
private $_persisterMock;
/**
* Enter description here...
*
* @param unknown_type $entityName
* @override
*/
public function getEntityPersister($entityName)
{
if ( ! $this->_persisterMock) {
$this->_persisterMock = new Doctrine_EntityPersisterMock($this, $this->getClassMetadata($entityName));
}
return $this->_persisterMock;
}
}
?>
\ No newline at end of file
<?php
class Doctrine_EntityPersisterMock extends Doctrine_EntityPersister_Standard
{
private $_inserts = array();
private $_updates = array();
private $_deletes = array();
private $_identityColumnValueCounter = 0;
public function insert($entity)
{
if ($entity->getClass()->isIdGeneratorIdentity()) {
$entity->_assignIdentifier($this->_identityColumnValueCounter++);
$this->_em->getUnitOfWork()->addToIdentityMap($entity);
}
$this->_inserts[] = $entity;
}
public function update($entity)
{
$this->_updates[] = $entity;
}
public function delete($entity)
{
$this->_deletes[] = $entity;
}
public function getInserts()
{
return $this->_inserts;
}
public function getUpdates()
{
return $this->_updates;
}
public function getDeletes()
{
return $this->_deletes;
}
public function reset()
{
$this->_identityColumnValueCounter = 0;
$this->_inserts = array();
$this->_updates = array();
$this->_deletes = array();
}
}
?>
\ No newline at end of file
<?php
class Doctrine_SequenceMock extends Doctrine_Sequence
{
private $_sequenceNumber = 0;
/**
* @override
*/
public function nextId($seqName, $ondemand = true)
{
return $this->_sequenceNumber++;
}
/**
* @override
*/
public function lastInsertId($table = null, $field = null)
{
return $this->_sequenceNumber - 1;
}
/**
* @override
*/
public function currId($seqName)
{
return $this->_sequenceNumber;
}
public function reset()
{
$this->_sequenceNumber = 0;
}
}
?>
\ No newline at end of file
<?php
#namespace Doctrine::Tests::ORM::Models::CMS;
#use Doctrine::ORM::Entity;
class CmsArticle extends Doctrine_Entity
{
public static function initMetadata($class)
#protected $id;
#protected $topic;
#protected $text;
#protected $user_id;
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary' => true, 'autoincrement' => true));
$class->mapColumn('topic', 'string', 255);
$class->mapColumn('text', 'string');
$class->mapColumn('user_id', 'integer', 4);
$class->hasMany('CmsComment as comments', array(
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true,
'generatorType' => 'auto'
));
$mapping->mapField(array(
'fieldName' => 'topic',
'type' => 'string',
'length' => 255
));
$mapping->mapField(array(
'fieldName' => 'text',
'type' => 'string'
));
$mapping->mapField(array(
'fieldName' => 'user_id',
'type' => 'integer',
'length' => 4
));
$mapping->hasMany('CmsComment as comments', array(
'local' => 'id', 'foreign' => 'article_id'));
}
}
<?php
#namespace Doctrine::Tests::ORM::Models::CMS;
#use Doctrine::ORM::Entity;
class CmsComment extends Doctrine_Entity
{
public static function initMetadata($class)
#protected $id;
#protected $topic;
#protected $text;
#protected $article_id;
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary' => true, 'autoincrement' => true));
$class->mapColumn('topic', 'string', 255);
$class->mapColumn('text', 'string');
$class->mapColumn('article_id', 'integer', 4);
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true,
'generatorType' => 'auto'
));
$mapping->mapField(array(
'fieldName' => 'topic',
'type' => 'string',
'length' => 255
));
$mapping->mapField(array(
'fieldName' => 'text',
'type' => 'string'
));
$mapping->mapField(array(
'fieldName' => 'article_id',
'type' => 'integer',
'length' => 4
));
}
}
<?php
class CmsPhonenumber extends Doctrine_Entity
{
public static function initMetadata($class)
#protected $user_id;
#protected $phonenumber;
public static function initMetadata($mapping)
{
$class->mapColumn('user_id', 'integer', 4);
$class->mapColumn('phonenumber', 'string', 50, array('primary' => true));
$mapping->mapField(array(
'fieldName' => 'user_id',
'type' => 'integer',
'length' => 4
));
$mapping->mapField(array(
'fieldName' => 'phonenumber',
'type' => 'string',
'length' => 50,
'id' => true
));
}
}
<?php
#namespace Doctrine::Test::ORM::Models;
#use Doctrine::ORM::Entity;
class CmsUser extends Doctrine_Entity
{
public static function initMetadata($class)
#protected $id;
#protected $status;
#protected $username;
#protected $name;
public static function initMetadata($mapping)
{
$class->mapColumn('id', 'integer', 4, array('primary' => true, 'autoincrement' => true));
$class->mapColumn('status', 'string', 50);
$class->mapColumn('username', 'string', 255);
$class->mapColumn('name', 'string', 255);
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true,
'generatorType' => 'auto'
));
$mapping->mapField(array(
'fieldName' => 'status',
'type' => 'string',
'length' => 50
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 255
));
$mapping->mapField(array(
'fieldName' => 'name',
'type' => 'string',
'length' => 255
));
$class->hasMany('CmsPhonenumber as phonenumbers', array(
$mapping->hasMany('CmsPhonenumber as phonenumbers', array(
'local' => 'id', 'foreign' => 'user_id'));
$class->hasMany('CmsArticle as articles', array(
$mapping->hasMany('CmsArticle as articles', array(
'local' => 'id', 'foreign' => 'user_id'));
}
}
......@@ -2,9 +2,14 @@
class ForumAdministrator extends ForumUser
{
public static function initMetadata($class)
public static function initMetadata($mapping)
{
$class->mapColumn('access_level as accessLevel', 'integer', 1);
$mapping->mapField(array(
'fieldName' => 'accessLevel',
'columnName' => 'access_level',
'type' => 'integer',
'length' => 1
));
}
public function banUser(ForumUser $user) {}
......
<?php
class ForumBoard extends Doctrine_Entity {
public static function initMetadata($metadata) {
class ForumBoard extends Doctrine_Entity
{
public static function initMetadata($mapping)
{
/*$metadata->mapField(array(
'fieldName' => 'id',
'id' => true,
......@@ -8,10 +10,22 @@ class ForumBoard extends Doctrine_Entity {
'length' => 4
));
*/
$metadata->mapColumn('id', 'integer', 4, array('primary'));
$metadata->mapColumn('position', 'integer');
$metadata->mapColumn('category_id', 'integer');
$metadata->hasOne('ForumCategory as category',
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'position',
'type' => 'integer'
));
$mapping->mapField(array(
'fieldName' => 'category_id',
'type' => 'integer'
));
$mapping->hasOne('ForumCategory as category',
array('local' => 'category_id', 'foreign' => 'id'));
/*
$metadata->mapOneToOne(array(
......
<?php
class ForumCategory extends Doctrine_Entity {
public static function initMetadata($class) {
$class->mapColumn('id', 'integer', 4, array('primary'));
$class->mapColumn('position', 'integer');
$class->mapColumn('name', 'string', 255);
$class->hasMany('ForumBoard as boards', array(
class ForumCategory extends Doctrine_Entity
{
public static function initMetadata($mapping)
{
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true
));
$mapping->mapField(array(
'fieldName' => 'position',
'type' => 'integer'
));
$mapping->mapField(array(
'fieldName' => 'name',
'type' => 'string',
'length' => 255
));
$mapping->hasMany('ForumBoard as boards', array(
'local' => 'id' , 'foreign' => 'category_id'));
}
}
<?php
#namespace Doctrine::Test::ORM::Models;
#use Doctrine::ORM::Entity;
class ForumEntry extends Doctrine_Entity
{
#protected $id;
#protected $topic;
public static function initMetadata($mapping)
{
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true,
'generatorType' => 'auto'
));
$mapping->mapField(array(
'fieldName' => 'topic',
'type' => 'string',
'length' => 50
));
}
}
?>
\ No newline at end of file
<?php
#namespace Doctrine::Tests::ORM::Models::Forum;
#use Doctrine::ORM::Entity;
class ForumUser extends Doctrine_Entity
{
public static function initMetadata($class)
#protected $dtype;
#protected $id;
#protected $username;
public static function initMetadata($mapping)
{
// inheritance mapping
$class->setInheritanceType(Doctrine::INHERITANCE_TYPE_JOINED, array(
$mapping->setInheritanceType('joined', array(
'discriminatorColumn' => 'dtype',
'discriminatorMap' => array(
'user' => 'ForumUser',
'admin' => 'ForumAdministrator')
));
// register subclasses
$class->setSubclasses(array('ForumAdministrator'));
$mapping->setSubclasses(array('ForumAdministrator'));
// the discriminator column
$class->mapColumn('dtype', 'string', 50);
$mapping->mapField(array(
'fieldName' => 'dtype',
'type' => 'string',
'length' => 50
));
// column-to-field mapping
$class->mapColumn('id', 'integer', 4, array(
'primary' => true,
'autoincrement' => true));
$class->mapColumn('username', 'string', 50, array());
$mapping->mapField(array(
'fieldName' => 'id',
'type' => 'integer',
'length' => 4,
'id' => true,
'generatorType' => 'auto'
));
$mapping->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'length' => 50
));
}
......
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