From d8b76a54d0d1f2f5aca84990bfd95e8214a1d8a7 Mon Sep 17 00:00:00 2001 From: romanb Date: Sat, 16 Aug 2008 19:40:59 +0000 Subject: [PATCH] continued refactorings. --- lib/Doctrine/Access.php | 3 +- lib/Doctrine/Association.php | 192 +++++++- lib/Doctrine/Association/OneToOne.php | 11 + lib/Doctrine/ClassMetadata.php | 419 +++++++---------- lib/Doctrine/ClassMetadata/Factory.php | 18 +- lib/Doctrine/Collection.php | 398 ++++++++++------ lib/Doctrine/CollectionPersister/Abstract.php | 48 ++ .../OneToManyPersister.php | 10 + lib/Doctrine/Connection/UnitOfWork.php | 69 ++- lib/Doctrine/DatabasePlatform.php | 20 + .../DatabasePlatform/MySqlPlatform.php | 19 + .../DatabasePlatform/OraclePlatform.php | 28 ++ lib/Doctrine/Entity.php | 431 +++++++++++------- lib/Doctrine/EntityManager.php | 112 +++-- lib/Doctrine/EntityPersister/Abstract.php | 176 ++++--- lib/Doctrine/EntityPersister/Standard.php | 3 - lib/Doctrine/EventManager.php | 15 +- lib/Doctrine/Exception.php | 2 +- lib/Doctrine/Hydrator/RecordDriver.php | 4 +- lib/Doctrine/HydratorNew.php | 4 +- lib/Doctrine/MappingException.php | 23 +- lib/Doctrine/Query/CacheHandler.php | 8 +- lib/Doctrine/Query/Production/Join.php | 4 +- .../Query/Production/PathExpression.php | 2 +- .../PathExpressionEndingWithAsterisk.php | 2 +- .../Production/RangeVariableDeclaration.php | 12 +- lib/Doctrine/Relation.php | 2 +- lib/Doctrine/Relation/Parser.php | 2 +- lib/Doctrine/Schema/MsSqlSchemaManager.php | 2 +- tests/Orm/AllTests.php | 5 +- .../Orm/Associations/OneToOneMappingTest.php | 7 +- tests/Orm/Component/AccessTest.php | 42 +- tests/Orm/EntityManagerTest.php | 20 + tests/Orm/EntityPersisterTest.php | 42 ++ tests/Orm/Hydration/BasicHydrationTest.php | 18 +- tests/Orm/Query/IdentifierRecognitionTest.php | 6 +- tests/lib/mocks/Doctrine_ConnectionMock.php | 19 + tests/models/cms/CmsArticle.php | 15 +- tests/models/cms/CmsUser.php | 16 +- tests/models/forum/ForumAvatar.php | 20 + tests/models/forum/ForumBoard.php | 13 +- tests/models/forum/ForumCategory.php | 9 +- tests/models/forum/ForumUser.php | 8 +- 43 files changed, 1483 insertions(+), 796 deletions(-) create mode 100644 lib/Doctrine/CollectionPersister/Abstract.php create mode 100644 lib/Doctrine/CollectionPersister/OneToManyPersister.php create mode 100644 tests/Orm/EntityManagerTest.php create mode 100644 tests/Orm/EntityPersisterTest.php create mode 100644 tests/models/forum/ForumAvatar.php diff --git a/lib/Doctrine/Access.php b/lib/Doctrine/Access.php index 1e62c53e5..56338637a 100644 --- a/lib/Doctrine/Access.php +++ b/lib/Doctrine/Access.php @@ -34,8 +34,7 @@ * @since 1.0 * @version $Revision$ * @author Konsta Vesterinen - * @todo Consider making Collection & Entity implement ArrayAccess directly. - * This base class is not really a huge benefit. + * @todo Remove. */ abstract class Doctrine_Access implements ArrayAccess { diff --git a/lib/Doctrine/Association.php b/lib/Doctrine/Association.php index df98efc60..bbc2aad58 100644 --- a/lib/Doctrine/Association.php +++ b/lib/Doctrine/Association.php @@ -24,8 +24,8 @@ /** * Base class for association mappings. * - * @since 2.0 * @author Roman Borschel + * @since 2.0 * @todo Rename to AssociationMapping. */ class Doctrine_Association implements Serializable @@ -52,6 +52,9 @@ class Doctrine_Association implements Serializable protected $_isCascadeSave; protected $_isCascadeRefresh; + protected $_customAccessor; + protected $_customMutator; + /** * The fetch mode used for the association. * @@ -67,6 +70,14 @@ class Doctrine_Association implements Serializable */ protected $_isOwningSide = true; + /** + * Whether the association is optional (0..X) or not (1..X). + * By default all associations are optional. + * + * @var boolean + */ + protected $_isOptional = true; + /** * The name of the source Entity (the Entity that defines this mapping). * @@ -84,7 +95,8 @@ class Doctrine_Association implements Serializable /** * Identifies the field on the source class (the class this AssociationMapping - * belongs to) that represents the association. + * belongs to) that represents the association and stores the reference to the + * other entity/entities. * * @var string */ @@ -106,12 +118,13 @@ class Doctrine_Association implements Serializable */ 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->_validateAndCompleteMapping($mapping); + + $this->_sourceEntityName = $mapping['sourceEntity']; + $this->_targetEntityName = $mapping['targetEntity']; + $this->_sourceFieldName = $mapping['fieldName']; + + if ( ! $this->_isOwningSide) { $this->_mappedByFieldName = $mapping['mappedBy']; } } @@ -122,23 +135,50 @@ class Doctrine_Association implements Serializable * @param array $mapping * @return array The validated & completed mapping. */ - protected function _validateMapping(array $mapping) - { + protected function _validateAndCompleteMapping(array $mapping) + { if (isset($mapping['mappedBy'])) { + // Inverse side, mapping can be found on the owning side. $this->_isOwningSide = false; + } else { + // Owning side } - if ($this->_isOwningSide) { - if ( ! isset($mapping['targetEntity'])) { - throw Doctrine_MappingException::missingTargetEntity(); - } else if ( ! isset($mapping['fieldName'])) { - throw Doctrine_MappingException::missingFieldName(); - } + // Mandatory attributes + if ( ! isset($mapping['fieldName'])) { + throw Doctrine_MappingException::missingFieldName(); } + if ( ! isset($mapping['sourceEntity'])) { + throw Doctrine_MappingException::missingSourceEntity($mapping['fieldName']); + } + if ( ! isset($mapping['targetEntity'])) { + throw Doctrine_MappingException::missingTargetEntity($mapping['fieldName']); + } + + // Optional attributes + $this->_customAccessor = isset($mapping['accessor']) ? $mapping['accessor'] : null; + $this->_customMutator = isset($mapping['mutator']) ? $mapping['mutator'] : null; + $this->_isOptional = isset($mapping['isOptional']) ? (bool)$mapping['isOptional'] : true; return $mapping; } + protected function _validateAndCompleteInverseSideMapping() + { + + } + + protected function _validateAndCompleteOwningSideMapping() + { + + } + + /** + * Whether the association cascades delete() operations from the source entity + * to the target entity/entities. + * + * @return boolean + */ public function isCascadeDelete() { if (is_null($this->_isCascadeDelete)) { @@ -147,6 +187,12 @@ class Doctrine_Association implements Serializable return $this->_isCascadeDelete; } + /** + * Whether the association cascades save() operations from the source entity + * to the target entity/entities. + * + * @return boolean + */ public function isCascadeSave() { if (is_null($this->_isCascadeSave)) { @@ -155,6 +201,12 @@ class Doctrine_Association implements Serializable return $this->_isCascadeSave; } + /** + * Whether the association cascades refresh() operations from the source entity + * to the target entity/entities. + * + * @return boolean + */ public function isCascadeRefresh() { if (is_null($this->_isCascadeRefresh)) { @@ -163,36 +215,81 @@ class Doctrine_Association implements Serializable return $this->_isCascadeRefresh; } + /** + * Whether the target entity/entities of the association are eagerly fetched. + * + * @return boolean + */ public function isEagerlyFetched() { return $this->_fetchMode == self::FETCH_EAGER; } + /** + * Whether the target entity/entities of the association are lazily fetched. + * + * @return boolean + */ public function isLazilyFetched() { return $this->_fetchMode == self::FETCH_LAZY; } + /** + * Whether the target entity/entities of the association are manually fetched. + * + * @return boolean + */ public function isManuallyFetched() { return $this->_fetchMode == self::FETCH_MANUAL; } + /** + * Whether the source entity of this association represents the owning side. + * + * @return boolean + */ public function isOwningSide() { return $this->_isOwningSide; } + /** + * Whether the source entity of this association represents the inverse side. + * + * @return boolean + */ public function isInverseSide() { return ! $this->_isOwningSide; } + /** + * Whether the association is optional (0..X), or not (1..X). + * + * @return boolean TRUE if the association is optional, FALSE otherwise. + */ + public function isOptional() + { + return $this->_isOptional; + } + + /** + * Gets the name of the source entity class. + * + * @return string + */ public function getSourceEntityName() { return $this->_sourceEntityName; } + /** + * Gets the name of the target entity class. + * + * @return string + */ public function getTargetEntityName() { return $this->_targetEntityName; @@ -201,17 +298,80 @@ class Doctrine_Association implements Serializable /** * Get the name of the field the association is mapped into. * + * @return string */ public function getSourceFieldName() { return $this->_sourceFieldName; } + /** + * Gets the field name of the owning side in a bi-directional association. + * + * @return string + */ public function getMappedByFieldName() { return $this->_mappedByFieldName; } + /** + * Whether the source field of the association has a custom accessor. + * + * @return boolean TRUE if the source field of the association has a custom accessor, + * FALSE otherwise. + */ + public function hasCustomAccessor() + { + return isset($this->_customAccessor); + } + + /** + * Gets the name of the custom accessor method of the source field. + * + * @return string The name of the accessor method or NULL. + */ + public function getCustomAccessor() + { + return $this->_customAccessor; + } + + /** + * Whether the source field of the association has a custom mutator. + * + * @return boolean TRUE if the source field of the association has a custom mutator, + * FALSE otherwise. + */ + public function hasCustomMutator() + { + return isset($this->_customMutator); + } + + /** + * Gets the name of the custom mutator method of the source field. + * + * @return string The name of the mutator method or NULL. + */ + public function getCustomMutator() + { + return $this->_customMutator; + } + + public function isOneToOne() + { + return false; + } + + public function isOneToMany() + { + return false; + } + + public function isManyToMany() + { + return false; + } + /* Serializable implementation */ public function serialize() diff --git a/lib/Doctrine/Association/OneToOne.php b/lib/Doctrine/Association/OneToOne.php index 8d1bb35e3..0af0b81a7 100644 --- a/lib/Doctrine/Association/OneToOne.php +++ b/lib/Doctrine/Association/OneToOne.php @@ -105,6 +105,17 @@ class Doctrine_Association_OneToOne extends Doctrine_Association return $this->_targetToSourceKeyColumns; } + /** + * Whether the association is one-to-one. + * + * @return boolean + * @override + */ + public function isOneToOne() + { + return true; + } + /** * Lazy-loads the associated entity for a given entity. * diff --git a/lib/Doctrine/ClassMetadata.php b/lib/Doctrine/ClassMetadata.php index 91191e834..b0b24169f 100644 --- a/lib/Doctrine/ClassMetadata.php +++ b/lib/Doctrine/ClassMetadata.php @@ -48,12 +48,18 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable const GENERATOR_TYPE_NONE = 'none'; /* The Entity types */ - // A regular entity is assumed to have persistent state that Doctrine should manage. + /** + * A regular entity is assumed to have persistent state that Doctrine should manage. + */ const ENTITY_TYPE_REGULAR = 'regular'; - // A transient entity is ignored by Doctrine. + /** + * A transient entity is ignored by Doctrine. + */ const ENTITY_TYPE_TRANSIENT = 'transient'; - // A mapped superclass entity is itself not persisted by Doctrine but it's - // field & association mappings are inherited by subclasses. + /** + * A mapped superclass entity is itself not persisted by Doctrine but it's + * field & association mappings are inherited by subclasses. + */ const ENTITY_TYPE_MAPPED_SUPERCLASS = 'mappedSuperclass'; /** @@ -122,15 +128,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * @var string */ protected $_generatorType = self::GENERATOR_TYPE_NONE; - - /** - * An array containing all behaviors attached to the class. - * - * @see Doctrine_Template - * @var array $_templates - * @todo Unify under 'Behaviors'. - */ - //protected $_behaviors = array(); /** * The field mappings of the class. @@ -218,13 +215,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * @var array */ protected $_lcColumnToFieldNames = array(); - - /** - * Enter description here... - * - * @var unknown_type - */ - //protected $_subclassFieldNames = array(); /** * Relation parser object. Manages the relations for the class. @@ -310,11 +300,15 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * @var boolean */ protected $_isIdentifierComposite = false; + + protected $_customAssociationAccessors = array(); + protected $_customAssociationMutators = array(); /** * Constructs a new ClassMetadata instance. * * @param string $entityName Name of the entity class the metadata info is used for. + * @param Doctrine::ORM::Entitymanager $em */ public function __construct($entityName, Doctrine_EntityManager $em) { @@ -324,15 +318,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $this->_parser = new Doctrine_Relation_Parser($this); } - - /** - * @deprecated - */ - public function getConnection() - { - return $this->_em; - } + /** + * Gets the EntityManager that holds this ClassMetadata. + * + * @return Doctrine::ORM::EntityManager + */ public function getEntityManager() { return $this->_em; @@ -360,14 +351,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return $this->_rootEntityName; } - /** - * @deprecated - */ - public function getComponentName() - { - return $this->getClassName(); - } - /** * Checks whether a field is part of the identifier/primary key field(s). * @@ -429,8 +412,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * addIndex - * * adds an index to this table * * @return void @@ -458,22 +439,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return false; } - /** - * setOption - * sets an option and returns this object in order to - * allow flexible method chaining - * - * @see Doctrine_Table::$_options for available options - * @param string $name the name of the option to set - * @param mixed $value the value of the option - * @return Doctrine_Table this object - * @deprecated - */ - public function setOption($name, $value) - { - $this->_options[$name] = $value; - } - /** * Sets a table option. */ @@ -497,43 +462,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return $this->_tableOptions[$name]; } - /*public function getBehaviorForMethod($method) - { - return (isset($this->_invokedMethods[$method])) ? - $this->_invokedMethods[$method] : false; - } - public function addBehaviorMethod($method, $behavior) - { - $this->_invokedMethods[$method] = $class; - }*/ - - /** - * returns the value of given option - * - * @param string $name the name of the option - * @return mixed the value of given option - */ - public function getOption($name) - { - if (isset($this->_options[$name])) { - return $this->_options[$name]; - } else if (isset($this->_tableOptions[$name])) { - return $this->_tableOptions[$name]; - } - return null; - } - - /** - * getOptions - * returns all options of this table and the associated values - * - * @return array all options and their values - */ - public function getOptions() - { - return $this->_options; - } - /** * getTableOptions * returns all table options. @@ -703,8 +631,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable /** * Validates & completes the field mapping. Default values are applied here. * - * @param array $mapping - * @return array + * @param array $mapping The field mapping to validated & complete. + * @return array The validated and completed field mapping. */ private function _validateAndCompleteFieldMapping(array $mapping) { @@ -800,18 +728,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable //... } - /** - * Gets the names of all validators that are applied on a field. - * - * @param string The field name. - * @return array The names of all validators that are applied on the specified field. - */ - /*public function getFieldValidators($fieldName) - { - return isset($this->_fieldMappings[$fieldName]['validators']) ? - $this->_fieldMappings[$fieldName]['validators'] : array(); - }*/ - /** * Gets the identifier (primary key) field names of the class. * @@ -871,8 +787,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getCustomAccessor($fieldName) { - return isset($this->_fieldMappings[$fieldName]['accessor']) ? - $this->_fieldMappings[$fieldName]['accessor'] : null; + if (isset($this->_fieldMappings[$fieldName]['accessor'])) { + return $this->_fieldMappings[$fieldName]['accessor']; + } else if (isset($this->_customAssociationAccessors[$fieldName])) { + return $this->_customAssociationAccessors[$fieldName]; + } + return null; } /** @@ -883,8 +803,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getCustomMutator($fieldName) { - return isset($this->_fieldMappings[$fieldName]['mutator']) ? - $this->_fieldMappings[$fieldName]['mutator'] : null; + if (isset($this->_fieldMappings[$fieldName]['mutator'])) { + return $this->_fieldMappings[$fieldName]['mutator']; + } else if (isset($this->_customAssociationMutators[$fieldName])) { + return $this->_customAssociationMutators[$fieldName]; + } + return null; } /** @@ -1053,18 +977,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable //... } - /** - * getBehaviors - * returns all behaviors attached to the class. - * - * @return array an array containing all templates - * @todo Unify under 'Behaviors' - */ - /*public function getBehaviors() - { - return $this->_behaviors; - }*/ - /** * Gets the inheritance type used by the class. * @@ -1156,8 +1068,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable throw Doctrine_MappingException::invalidInheritanceType($type); } - if ($type == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE || - $type == Doctrine::INHERITANCE_TYPE_JOINED) { + if ($type == self::INHERITANCE_TYPE_SINGLE_TABLE || + $type == self::INHERITANCE_TYPE_JOINED) { $this->_checkRequiredDiscriminatorOptions($options); } @@ -1240,25 +1152,25 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * export * exports this class to the database based on its mapping. * * @throws Doctrine_Connection_Exception If some error other than Doctrine::ERR_ALREADY_EXISTS * occurred during the create table operation. * @return boolean Whether or not the export operation was successful * false if table already existed in the database. + * @todo Reimpl. & Placement. */ public function export() { - $this->_em->export->exportTable($this); + //$this->_em->export->exportTable($this); } /** - * getExportableFormat * Returns an array with all the information needed to create the main database table * for the class. * * @return array + * @todo Reimpl. & placement. */ public function getExportableFormat($parseForeignKeys = true) { @@ -1268,7 +1180,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable // If the class is part of a Single Table Inheritance hierarchy, collect the fields // of all classes in the hierarchy. - if ($this->_inheritanceType == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) { + if ($this->_inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE) { $parents = $this->getParentClasses(); if ($parents) { $rootClass = $this->_em->getClassMetadata(array_pop($parents)); @@ -1280,7 +1192,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $subClassMetadata = $this->_em->getClassMetadata($subClass); $allColumns = array_merge($allColumns, $subClassMetadata->getColumns()); } - } else if ($this->_inheritanceType == Doctrine::INHERITANCE_TYPE_JOINED) { + } else if ($this->_inheritanceType == self::INHERITANCE_TYPE_JOINED) { // Remove inherited, non-pk fields. They're not in the table of this class foreach ($allColumns as $name => $definition) { if (isset($definition['primary']) && $definition['primary'] === true) { @@ -1293,7 +1205,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable unset($allColumns[$name]); } } - } else if ($this->_inheritanceType == Doctrine::INHERITANCE_TYPE_TABLE_PER_CLASS) { + } else if ($this->_inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS) { // If this is a subclass, just remove existing autoincrement options on the pk if ($this->getParentClasses()) { foreach ($allColumns as $name => $definition) { @@ -1423,7 +1335,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * Checks whether the given name identifies an entity type. + * Checks whether the given type identifies an entity type. * * @param string $type * @return boolean @@ -1436,7 +1348,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * Checks whether the given name identifies an inheritance type. + * Checks whether the given type identifies an inheritance type. * * @param string $type * @return boolean @@ -1450,7 +1362,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * Checks whether the given name identifies an id generator type. + * Checks whether the given type identifies an id generator type. * * @param string $type * @return boolean @@ -1463,7 +1375,38 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $type == self::GENERATOR_TYPE_TABLE || $type == self::GENERATOR_TYPE_NONE; } - + + /** + * Makes some automatic additions to the association mapping to make the life + * easier for the user. + * + * @param array $mapping + * @return unknown + * @todo Pass param by ref? + */ + private function _completeAssociationMapping(array $mapping) + { + $mapping['sourceEntity'] = $this->_entityName; + return $mapping; + } + + /** + * Registers any custom accessors/mutators in the given association mapping in + * an internal cache for fast lookup. + * + * @param Doctrine_Association $assoc + * @param unknown_type $fieldName + */ + private function _registerCustomAssociationAccessors(Doctrine_Association $assoc, $fieldName) + { + if ($acc = $assoc->getCustomAccessor()) { + $this->_customAssociationAccessors[$fieldName] = $acc; + } + if ($mut = $assoc->getCustomMutator()) { + $this->_customAssociationMutators[$fieldName] = $mut; + } + } + /** * Adds a one-to-one association mapping. * @@ -1471,11 +1414,14 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function mapOneToOne(array $mapping) { + $mapping = $this->_completeAssociationMapping($mapping); $oneToOneMapping = new Doctrine_Association_OneToOne($mapping); - if (isset($this->_associationMappings[$oneToOneMapping->getSourceFieldName()])) { + $sourceFieldName = $oneToOneMapping->getSourceFieldName(); + if (isset($this->_associationMappings[$sourceFieldName])) { throw Doctrine_MappingException::duplicateFieldMapping(); } - $this->_associationMappings[$oneToOneMapping->getSourceFieldName()] = $oneToOneMapping; + $this->_associationMappings[$sourceFieldName] = $oneToOneMapping; + $this->_registerCustomAssociationAccessors($oneToOneMapping, $sourceFieldName); } /** @@ -1483,7 +1429,14 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function mapOneToMany(array $mapping) { - + $mapping = $this->_completeAssociationMapping($mapping); + $oneToManyMapping = new Doctrine_Association_OneToMany($mapping); + $sourceFieldName = $oneToManyMapping->getSourceFieldName(); + if (isset($this->_associationMappings[$sourceFieldName])) { + throw Doctrine_MappingException::duplicateFieldMapping(); + } + $this->_associationMappings[$sourceFieldName] = $oneToManyMapping; + $this->_registerCustomAssociationAccessors($oneToManyMapping, $sourceFieldName); } /** @@ -1501,80 +1454,11 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable { } - - /** - * actAs - * loads the given plugin - * - * @param mixed $tpl - * @param array $options - * @todo Unify under 'Behaviors'. - */ - /*public function actAs($tpl, array $options = array()) - { - if ( ! is_object($tpl)) { - if (class_exists($tpl, true)) { - $tpl = new $tpl($options); - } else { - $className = 'Doctrine_Template_' . $tpl; - if ( ! class_exists($className, true)) { - throw new Doctrine_Record_Exception("Couldn't load plugin."); - } - $tpl = new $className($options); - } - } - - if ( ! ($tpl instanceof Doctrine_Template)) { - throw new Doctrine_Record_Exception('Loaded plugin class is not an instance of Doctrine_Template.'); - } - - $className = get_class($tpl); - - $this->addBehavior($className, $tpl); - - $tpl->setTable($this); - $tpl->setUp(); - $tpl->setTableDefinition(); - - return $this; - }*/ - - /** - * check - * adds a check constraint - * - * @param mixed $constraint either a SQL constraint portion or an array of CHECK constraints - * @param string $name optional constraint name - * @return Doctrine_Entity this object - * @todo Should be done through $_tableOptions - * @deprecated - */ - /*public function check($constraint, $name = null) - { - if (is_array($constraint)) { - foreach ($constraint as $name => $def) { - $this->_addCheckConstraint($def, $name); - } - } else { - $this->_addCheckConstraint($constraint, $name); - } - - return $this; - } - protected function _addCheckConstraint($definition, $name) - { - if (is_string($name)) { - $this->_tableOptions['checks'][$name] = $definition; - } else { - $this->_tableOptions['checks'][] = $definition; - } - }*/ /** * Registers a custom mapper for the entity class. * * @param string $mapperClassName The class name of the custom mapper. - * @deprecated */ public function setCustomRepositoryClass($repositoryClassName) { @@ -1607,7 +1491,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * Binds the entity instance of this class to a specific EntityManager. + * Binds the entity instances of this class to a specific EntityManager. * * @todo Implementation. Replaces the bindComponent() methods on the old Doctrine_Manager. * Binding an Entity to a specific EntityManager in 2.0 is the same as binding @@ -1697,6 +1581,24 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $this->_lifecycleCallbacks[$event][$callback] = $callback; } } + + /** + * Completes the identifier mapping of the class. + * NOTE: Should only be called by the ClassMetadataFactory! + */ + public function completeIdentifierMapping() + { + if ($this->getIdGeneratorType() == self::GENERATOR_TYPE_AUTO) { + $platform = $this->_em->getConnection()->getDatabasePlatform(); + if ($platform->prefersSequences()) { + $this->_generatorType = self::GENERATOR_TYPE_SEQUENCE; + } else if ($platform->prefersIdentityColumns()) { + $this->_generatorType = self::GENERATOR_TYPE_IDENTITY; + } else { + $this->_generatorType = self::GENERATOR_TYPE_TABLE; + } + } + } /** * @todo Implementation. Immutable entities can not be updated or deleted once @@ -1713,38 +1615,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return $columnName === $this->_inheritanceOptions['discriminatorColumn']; } - /** - * hasOne - * binds One-to-One aggregate relation - * - * @param string $componentName the name of the related component - * @param string $options relation options - * @see Doctrine_Relation::_$definition - * @return Doctrine_Entity this object - */ - public function hasOne() - { - $this->bind(func_get_args(), Doctrine_Relation::ONE_AGGREGATE); - - return $this; - } - - /** - * hasMany - * binds One-to-Many / Many-to-Many aggregate relation - * - * @param string $componentName the name of the related component - * @param string $options relation options - * @see Doctrine_Relation::_$definition - * @return Doctrine_Entity this object - */ - public function hasMany() - { - $this->bind(func_get_args(), Doctrine_Relation::MANY_AGGREGATE); - - return $this; - } - public function hasAttribute($name) { return isset($this->_attributes[$name]); @@ -1766,6 +1636,43 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable /* The following stuff needs to be touched for the association mapping rewrite */ + /** + * hasOne + * binds One-to-One aggregate relation + * + * @param string $componentName the name of the related component + * @param string $options relation options + * @see Doctrine_Relation::_$definition + * @return Doctrine_Entity this object + * @deprecated + */ + public function hasOne() + { + $this->bind(func_get_args(), Doctrine_Relation::ONE_AGGREGATE); + + return $this; + } + + /** + * hasMany + * binds One-to-Many / Many-to-Many aggregate relation + * + * @param string $componentName the name of the related component + * @param string $options relation options + * @see Doctrine_Relation::_$definition + * @return Doctrine_Entity this object + * @deprecated + */ + public function hasMany() + { + $this->bind(func_get_args(), Doctrine_Relation::MANY_AGGREGATE); + + return $this; + } + + /** + * @deprecated + */ public function bindRelation($args, $type) { return $this->bind($args, $type); @@ -1773,6 +1680,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable /** * @todo Relation mapping rewrite. + * @deprecated */ public function bind($args, $type) { @@ -1798,22 +1706,32 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * * @param string $alias the relation to check if exists * @return boolean true if the relation exists otherwise false + * @deprecated */ public function hasRelation($alias) { return $this->_parser->hasRelation($alias); } + + public function hasAssociation($fieldName) + { + return isset($this->_associationMappings[$fieldName]); + } /** * getRelation * * @param string $alias relation alias + * @deprecated */ public function getRelation($alias, $recursive = true) { return $this->_parser->getRelation($alias, $recursive); } - + + /** + * @deprecated + */ public function getRelationParser() { return $this->_parser; @@ -1824,33 +1742,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * returns an array containing all relation objects * * @return array an array of Doctrine_Relation objects + * @deprecated */ public function getRelations() { 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; - } - - public function completeIdentifierMapping() - { - if ($this->getIdGeneratorType() == self::GENERATOR_TYPE_AUTO) { - $platform = $this->_em->getConnection()->getDatabasePlatform(); - if ($platform->prefersSequences()) { - $this->_generatorType = self::GENERATOR_TYPE_SEQUENCE; - } else if ($platform->prefersIdentityColumns()) { - $this->_generatorType = self::GENERATOR_TYPE_IDENTITY; - } else { - $this->_generatorType = self::GENERATOR_TYPE_TABLE; - } - } - } /** * diff --git a/lib/Doctrine/ClassMetadata/Factory.php b/lib/Doctrine/ClassMetadata/Factory.php index 6a21f310e..c0ff594c6 100644 --- a/lib/Doctrine/ClassMetadata/Factory.php +++ b/lib/Doctrine/ClassMetadata/Factory.php @@ -23,7 +23,7 @@ /** * The metadata factory is used to create ClassMetadata objects that contain all the - * metadata of a class. + * metadata mapping informations of a class. * * @author Konsta Vesterinen * @author Roman Borschel @@ -45,7 +45,7 @@ class Doctrine_ClassMetadata_Factory /** * Constructor. - * Creates a new factory instance that uses the given connection and metadata driver + * Creates a new factory instance that uses the given EntityManager and metadata driver * implementations. * * @param $conn The connection to use. @@ -129,6 +129,12 @@ class Doctrine_ClassMetadata_Factory } } + /** + * Adds inherited fields to the subclass mapping. + * + * @param unknown_type $subClass + * @param unknown_type $parentClass + */ protected function _addInheritedFields($subClass, $parentClass) { foreach ($parentClass->getFieldMappings() as $fieldName => $mapping) { @@ -138,6 +144,12 @@ class Doctrine_ClassMetadata_Factory } } + /** + * Adds inherited associations to the subclass mapping. + * + * @param unknown_type $subClass + * @param unknown_type $parentClass + */ protected function _addInheritedRelations($subClass, $parentClass) { foreach ($parentClass->getRelationParser()->getRelationDefinitions() as $name => $definition) { @@ -177,7 +189,7 @@ class Doctrine_ClassMetadata_Factory // save parents $class->setParentClasses($names); - // load further metadata + // load user-specified mapping metadata through the driver $this->_driver->loadMetadataForClass($name, $class); // set default table name, if necessary diff --git a/lib/Doctrine/Collection.php b/lib/Doctrine/Collection.php index 8f08ccbd6..f35fcdc21 100644 --- a/lib/Doctrine/Collection.php +++ b/lib/Doctrine/Collection.php @@ -23,26 +23,42 @@ /** * A persistent collection of entities. + * * A collection object is strongly typed in the sense that it can only contain - * entities of a specific type or one it's subtypes. + * entities of a specific type or one of it's subtypes. A collection object is + * basically a wrapper around an ordinary php array and just like a php array + * it can have List or Map semantics. + * + * A collection of entities represents only the associations (links) to those entities. + * That means, if the collection is part of a many-many mapping and you remove + * entities from the collection, only the links in the xref table are removed. + * Similarly, if you remove entities from a collection that is part of a one-many + * mapping this will only result in the nulling out of the foreign keys + * (or removal of the links in the xref table if the one-many is mapped through an + * xref table). If you want entities in a one-many collection to be removed when + * they're removed from the collection, use deleteOrphans => true on the one-many + * mapping. * - * @package Doctrine - * @subpackage Collection - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.org - * @since 1.0 - * @version $Revision$ - * @author Konsta Vesterinen + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @version $Revision$ + * @author Konsta Vesterinen + * @author Roman Borschel */ -class Doctrine_Collection extends Doctrine_Access implements Countable, IteratorAggregate, Serializable +class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, ArrayAccess { - + /** + * The base type of the collection. + * + * @var string + */ protected $_entityBaseType; /** - * An array containing the records of this collection. + * An array containing the entries of this collection. + * This is the wrapped php array. * - * @var array + * @var array */ protected $_data = array(); @@ -83,20 +99,17 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator */ //protected static $null; + protected $_mapping; + /** * The EntityManager. * * @var EntityManager */ protected $_em; - protected $_isDirty = false; /** * Constructor. - * - * @param Doctrine_Mapper|string $mapper The mapper used by the collection. - * @param string $keyColumn The field name that will be used as the key - * in the collection. */ public function __construct($entityBaseType, $keyField = null) { @@ -115,7 +128,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * setData * * @param array $data - * @return Doctrine_Collection */ public function setData(array $data) { @@ -124,7 +136,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator /** * Serializes the collection. - * This method is automatically called when this Doctrine_Collection is serialized. + * This method is automatically called when the Collection is serialized. * * Part of the implementation of the Serializable interface. * @@ -145,7 +157,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator /** * Reconstitutes the collection object from it's serialized form. - * This method is automatically called everytime a Doctrine_Collection object is unserialized. + * This method is automatically called everytime the Collection object is unserialized. * * Part of the implementation of the Serializable interface. * @@ -172,8 +184,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * setKeyField - * sets the key column for this collection + * Sets the key column for this collection * * @param string $column * @return Doctrine_Collection @@ -185,7 +196,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getKeyField * returns the name of the key column * * @return string @@ -196,7 +206,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getData * returns all the records as an array * * @return array @@ -207,7 +216,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getFirst * returns the first record in the collection * * @return mixed @@ -218,7 +226,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getLast * returns the last record in the collection * * @return mixed @@ -254,7 +261,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * * @return void */ - public function setReference(Doctrine_Entity $entity, Doctrine_Relation $relation) + public function setReference(Doctrine_Entity $entity, $relation) { $this->_owner = $entity; //$this->relation = $relation; @@ -296,21 +303,122 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator { $removed = $this->_data[$key]; unset($this->_data[$key]); + //TODO: Register collection as dirty with the UoW if necessary return $removed; } + + /** + * __isset() + * + * @param string $name + * @return boolean whether or not this object contains $name + */ + public function __isset($key) + { + return $this->containsKey($key); + } + + /** + * __unset() + * + * @param string $name + * @since 1.0 + * @return void + */ + public function __unset($key) + { + return $this->remove($key); + } + + /** + * Check if an offsetExists. + * + * Part of the ArrayAccess implementation. + * + * @param mixed $offset + * @return boolean whether or not this object contains $offset + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * offsetGet an alias of get() + * + * Part of the ArrayAccess implementation. + * + * @see get, __get + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Part of the ArrayAccess implementation. + * + * sets $offset to $value + * @see set, __set + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + return $this->add($value); + } + return $this->set($offset, $value); + } + + /** + * Part of the ArrayAccess implementation. + * + * unset a given offset + * @see set, offsetSet, __set + * @param mixed $offset + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } /** * Checks whether the collection contains an entity. * * @param mixed $key the key of the element * @return boolean - * @todo Rename to containsKey(). */ - public function contains($key) + public function containsKey($key) { return isset($this->_data[$key]); } + /** + * Enter description here... + * + * @param unknown_type $entity + * @return unknown + */ + public function contains($entity) + { + return in_array($entity, $this->_data, true); + } + + /** + * Enter description here... + * + * @param unknown_type $otherColl + * @todo Impl + */ + public function containsAll($otherColl) + { + //... + } + /** * */ @@ -336,50 +444,26 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * Returns the primary keys of all records in the collection. - * - * @return array An array containing all primary keys. - * @todo Rename. - */ - public function getPrimaryKeys() - { - $list = array(); - $idFieldNames = (array)$this->_mapper->getClassMetadata()->getIdentifier(); - - foreach ($this->_data as $record) { - if (is_array($record)) { - if (count($idFieldNames) > 1) { - $id = array(); - foreach ($idFieldNames as $fieldName) { - if (isset($record[$fieldName])) { - $id[] = $record[$fieldName]; - } - } - $list[] = $id; - } else { - $idField = $idFieldNames[0]; - if (isset($record[$idField])) { - $list[] = $record[$idField]; - } - } - } else { - // @todo does not take composite keys into account - $ids = $record->identifier(); - $list[] = count($ids) > 0 ? array_pop($ids) : null; - } - } - - return $list; - } - - /** - * returns all keys + * Gets all keys. + * (Map method) + * * @return array */ public function getKeys() { return array_keys($this->_data); } + + /** + * Gets all values. + * (Map method) + * + * @return array + */ + public function getValues() + { + return array_values($this->_data); + } /** * Returns the number of records in this collection. @@ -394,45 +478,39 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * set + * When the collection is a Map this is like put(key,value)/add(key,value). + * When the collection is a List this is like add(position,value). + * * @param integer $key - * @param Doctrine_Entity $record + * @param mixed $value * @return void - * @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, $value) { if ( ! $value instanceof Doctrine_Entity) { throw new Doctrine_Collection_Exception('Value variable in set is not an instance of Doctrine_Entity'); } - $this->_data[$key] = $value; + //TODO: Register collection as dirty with the UoW if necessary + $this->_changed(); } /** - * adds a record to collection + * Adds an entry to the collection. * - * @param Doctrine_Entity $record record to be added - * @param string $key optional key for the record + * @param mixed $value + * @param string $key * @return boolean */ public function add($value, $key = null) { - /** @TODO Use raw getters/setters */ if ( ! $value instanceof Doctrine_Entity) { throw new Doctrine_Record_Exception('Value variable in set is not an instance of Doctrine_Entity.'); } - /* - * 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 === $value) { - return false; - } + // Neither Maps nor Lists allow duplicates, both are Sets + if (in_array($value, $this->_data, true)) { + return false; } if (isset($key)) { @@ -440,13 +518,28 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator return false; } $this->_data[$key] = $value; - return true; + } else { + $this->_data[] = $value; } - - $this->_data[] = $value; + + //TODO: Register collection as dirty with the UoW if necessary + $this->_changed(); return true; } + + /** + * Adds all entities of the other collection to this collection. + * + * @param unknown_type $otherCollection + * @todo Impl + */ + public function addAll($otherCollection) + { + //... + //TODO: Register collection as dirty with the UoW if necessary + //$this->_changed(); + } /** * INTERNAL: @@ -454,6 +547,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * * @param mixed $name * @return boolean + * @todo New implementation & maybe move elsewhere. */ public function loadRelated($name = null) { @@ -506,8 +600,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * @param string $name * @param Doctrine_Collection $coll * @return void + * @todo New implementation & maybe move elsewhere. */ - public function populateRelated($name, Doctrine_Collection $coll) + protected function populateRelated($name, Doctrine_Collection $coll) { $rel = $this->_mapper->getTable()->getRelation($name); $table = $rel->getTable(); @@ -562,38 +657,23 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getNormalIterator - * returns normal iterator - an iterator that will not expand this collection + * INTERNAL: Takes a snapshot from this collection. * - * @return Doctrine_Iterator_Normal - */ - public function getNormalIterator() - { - return new Doctrine_Collection_Iterator_Normal($this); - } - - /** - * takeSnapshot - * takes a snapshot from this collection - * - * snapshots are used for diff processing, for example + * Snapshots are used for diff processing, for example * when a fetched collection has three elements, then two of those - * are being removed the diff would contain one element + * are being removed the diff would contain one element. * - * Doctrine_Collection::save() attaches the diff with the help of last - * snapshot. - * - * @return Doctrine_Collection + * Collection::save() attaches the diff with the help of last snapshot. + * + * @return void */ public function takeSnapshot() { $this->_snapshot = $this->_data; - return $this; } /** - * getSnapshot - * returns the data of the last snapshot + * INTERNAL: Returns the data of the last snapshot. * * @return array returns the data in last snapshot */ @@ -603,8 +683,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * processDiff - * processes the difference of the last snapshot and the current data + * Processes the difference of the last snapshot and the current data. * * an example: * Snapshot with the objects 1, 2 and 4 @@ -623,7 +702,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * toArray * Mimics the result of a $query->execute(array(), Doctrine::FETCH_ARRAY); * * @param boolean $deep @@ -639,15 +717,18 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator return $data; } + /** + * Checks whether the collection is empty. + * + * @return boolean TRUE if the collection is empty, FALSE otherwise. + */ public function isEmpty() { return $this->count() == 0; } /** - * fromArray - * - * Populate a Doctrine_Collection from an array of data + * Populate a Doctrine_Collection from an array of data. * * @param string $array * @return void @@ -661,8 +742,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * synchronizeFromArray - * synchronizes a Doctrine_Collection with data from an array + * Synchronizes a Doctrine_Collection with data from an array. * * it expects an array representation of a Doctrine_Collection similar to the return * value of the toArray() method. It will create Dectrine_Records that don't exist @@ -689,8 +769,6 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * exportTo - * * Export a Doctrine_Collection to one of the supported Doctrine_Parser formats * * @param string $type @@ -698,18 +776,16 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * @return void * @todo Move elsewhere. */ - public function exportTo($type, $deep = false) + /*public function exportTo($type, $deep = false) { if ($type == 'array') { return $this->toArray($deep); } else { return Doctrine_Parser::dump($this->toArray($deep, true), $type); } - } + }*/ /** - * importFrom - * * Import data to a Doctrine_Collection from one of the supported Doctrine_Parser formats * * @param string $type @@ -717,19 +793,19 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * @return void * @todo Move elsewhere. */ - public function importFrom($type, $data) + /*public function importFrom($type, $data) { if ($type == 'array') { return $this->fromArray($data); } else { return $this->fromArray(Doctrine_Parser::load($data, $type)); } - } + }*/ /** - * getDeleteDiff + * INTERNAL: getDeleteDiff * - * @return void + * @return array */ public function getDeleteDiff() { @@ -737,9 +813,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * getInsertDiff + * INTERNAL getInsertDiff * - * @return void + * @return array */ public function getInsertDiff() { @@ -747,8 +823,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * _compareRecords * Compares two records. To be used on _snapshot diffs using array_udiff. + * + * @return integer */ protected function _compareRecords($a, $b) { @@ -766,7 +843,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator * @param Doctrine_Connection $conn optional connection parameter * @return Doctrine_Collection */ - public function save() + /*public function save() { $conn = $this->_mapper->getConnection(); @@ -786,14 +863,15 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } return $this; - } + }*/ /** * Deletes all records from the collection. + * Shorthand for calling delete() for all entities in the collection. * * @return void */ - public function delete($clearColl = false) + /*public function delete() { $conn = $this->_mapper->getConnection(); @@ -811,10 +889,8 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator throw $e; } - if ($clearColl) { - $this->clear(); - } - } + $this->clear(); + }*/ public function free($deep = false) @@ -853,16 +929,54 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator } /** - * returns the relation object - * @return object Doctrine_Relation + * Gets the association mapping of the collection. + * + * @return Doctrine::ORM::Mapping::AssociationMapping */ - public function getRelation() + public function getMapping() { return $this->relation; } + /** + * @todo Experiment. Waiting for 5.3 closures. + * Example usage: + * + * $map = $coll->mapElements(function($key, $entity) { + * return array($entity->id, $entity->name); + * }); + * + * or: + * + * $map = $coll->mapElements(function($key, $entity) { + * return array($entity->name, strtoupper($entity->name)); + * }); + * + */ + public function mapElements($lambda) { + $result = array(); + foreach ($this->_data as $key => $entity) { + list($key, $value) = $lambda($key, $entity); + $result[$key] = $value; + } + return $result; + } + + /** + * Clears the collection. + * + * @return void + */ public function clear() { $this->_data = array(); + //TODO: Register collection as dirty with the UoW if necessary + } + + private function _changed() + { + /*if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) { + $this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); + }*/ } } diff --git a/lib/Doctrine/CollectionPersister/Abstract.php b/lib/Doctrine/CollectionPersister/Abstract.php new file mode 100644 index 000000000..4aa1f6102 --- /dev/null +++ b/lib/Doctrine/CollectionPersister/Abstract.php @@ -0,0 +1,48 @@ +getRelation()->isInverseSide()) { + return; + } + + //... + } + + public function delete(Doctrine_Collection $coll) + { + if ($coll->getRelation()->isInverseSide()) { + return; + } + + //... + if ($coll->getRelation() instanceof Doctrine_Association_OneToManyMapping) { + //... + } else if ($coll->getRelation() instanceof Doctrine_Association_ManyToManyMapping) { + //... + } + } + + /* collection update actions */ + + public function deleteRows() + { + + } + + public function updateRows() + { + + } + + public function insertRows() + { + + } + +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/CollectionPersister/OneToManyPersister.php b/lib/Doctrine/CollectionPersister/OneToManyPersister.php new file mode 100644 index 000000000..e43f903b5 --- /dev/null +++ b/lib/Doctrine/CollectionPersister/OneToManyPersister.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/lib/Doctrine/Connection/UnitOfWork.php b/lib/Doctrine/Connection/UnitOfWork.php index e10c51b71..5a50e7618 100644 --- a/lib/Doctrine/Connection/UnitOfWork.php +++ b/lib/Doctrine/Connection/UnitOfWork.php @@ -77,6 +77,27 @@ class Doctrine_Connection_UnitOfWork * @todo Rename to _deletions? */ protected $_deletedEntities = array(); + + /** + * All collection deletions. + * + * @var array + */ + protected $_collectionDeletions = array(); + + /** + * All collection creations. + * + * @var array + */ + protected $_collectionCreations = array(); + + /** + * All collection updates. + * + * @var array + */ + protected $_collectionUpdates = array(); /** * The EntityManager the UnitOfWork belongs to. @@ -115,16 +136,16 @@ class Doctrine_Connection_UnitOfWork public function commit() { // Detect changes in managed entities (mark dirty) - //TODO: Consider using registerDirty() in Entity#set() instead if its - // more performant. - foreach ($this->_identityMap as $entities) { + //TODO: Consider using registerDirty() in Entity#_set() instead if its + // more performant (SEE THERE). + /*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) && @@ -141,8 +162,12 @@ class Doctrine_Connection_UnitOfWork $this->_executeInserts($class); $this->_executeUpdates($class); } + + //TODO: collection deletions + //TODO: collection updates (deleteRows, updateRows, insertRows) + //TODO: collection recreations - // Deletions come last and need to be in reverse commit order + // Entity 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]); } @@ -232,7 +257,7 @@ class Doctrine_Connection_UnitOfWork foreach ($node->getClass()->getAssociationMappings() as $assocMapping) { //TODO: should skip target classes that are not in the changeset. if ($assocMapping->isOwningSide()) { - $targetClass = $assocMapping->getTargetClass(); + $targetClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); $targetClassName = $targetClass->getClassName(); // if the target class does not yet have a node, create it if ( ! $this->_commitOrderCalculator->hasNodeWithKey($targetClassName)) { @@ -711,6 +736,38 @@ class Doctrine_Connection_UnitOfWork $this->_commitOrderCalculator->clear(); } + public function scheduleCollectionUpdate(Doctrine_Collection $coll) + { + $this->_collectionUpdates[] = $coll; + } + + public function isCollectionScheduledForUpdate(Doctrine_Collection $coll) + { + //... + } + + public function scheduleCollectionDeletion(Doctrine_Collection $coll) + { + //TODO: if $coll is already scheduled for recreation ... what to do? + // Just remove $coll from the scheduled recreations? + $this->_collectionDeletions[] = $coll; + } + + public function isCollectionScheduledForDeletion(Doctrine_Collection $coll) + { + //... + } + + public function scheduleCollectionRecreation(Doctrine_Collection $coll) + { + $this->_collectionRecreations[] = $coll; + } + + public function isCollectionScheduledForRecreation(Doctrine_Collection $coll) + { + //... + } + // Stuff from 0.11/1.0 that we will need later (need to modify it though) diff --git a/lib/Doctrine/DatabasePlatform.php b/lib/Doctrine/DatabasePlatform.php index 642173765..7f7ef14b6 100644 --- a/lib/Doctrine/DatabasePlatform.php +++ b/lib/Doctrine/DatabasePlatform.php @@ -1,4 +1,23 @@ . + */ #namespace Doctrine::DBAL::Platforms; @@ -9,6 +28,7 @@ * * @since 2.0 * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) */ abstract class Doctrine_DatabasePlatform { diff --git a/lib/Doctrine/DatabasePlatform/MySqlPlatform.php b/lib/Doctrine/DatabasePlatform/MySqlPlatform.php index 57694754a..d82de8496 100644 --- a/lib/Doctrine/DatabasePlatform/MySqlPlatform.php +++ b/lib/Doctrine/DatabasePlatform/MySqlPlatform.php @@ -1,4 +1,23 @@ . + */ /** * The MySqlPlatform provides the behavior, features and SQL dialect of the diff --git a/lib/Doctrine/DatabasePlatform/OraclePlatform.php b/lib/Doctrine/DatabasePlatform/OraclePlatform.php index 3f7cec6bc..6818443ac 100644 --- a/lib/Doctrine/DatabasePlatform/OraclePlatform.php +++ b/lib/Doctrine/DatabasePlatform/OraclePlatform.php @@ -1,5 +1,33 @@ . + */ +/** + * Base class for all DatabasePlatforms. The DatabasePlatforms are the central + * point of abstraction of platform-specific behaviors, features and SQL dialects. + * They are a passive source of information. + * + * @since 2.0 + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + */ class Doctrine_DatabasePlatform_OraclePlatform extends Doctrine_DatabasePlatform { /** diff --git a/lib/Doctrine/Entity.php b/lib/Doctrine/Entity.php index 2deb32c7a..5e87b132d 100644 --- a/lib/Doctrine/Entity.php +++ b/lib/Doctrine/Entity.php @@ -23,7 +23,7 @@ /** * Base class for all Entities (objects with persistent state in a RDBMS that are - * managed by Doctrine). + * managed by Doctrine). Kind of a Layer Suptertype. * * NOTE: Methods that are intended for internal use only but must be public * are marked INTERNAL: and begin with an underscore "_" to indicate that they @@ -40,7 +40,7 @@ * @since 2.0 * @version $Revision: 4342 $ */ -abstract class Doctrine_Entity extends Doctrine_Access implements Serializable +abstract class Doctrine_Entity implements ArrayAccess, Serializable { /** * MANAGED @@ -145,23 +145,37 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable private $_state; /** - * The names of fields that have been modified but not yet persisted. + * The changes that happened to fields of the entity. * Keys are field names, values oldValue => newValue tuples. * * @var array - * @todo Rename to $_changeSet */ - private $_modified = array(); + private $_dataChangeSet = array(); + + /** + * The changes that happened to references of the entity to other entities. + * Keys are field names, values oldReference => newReference tuples. + * + * With one-one associations, a reference change means the reference has been + * swapped out / replaced by another one. + * + * With one-many, many-many associations, a reference change means the complete + * collection has been sweapped out / replaced by another one. + * + * @var array + */ + private $_referenceChangeSet = array(); /** * The references for all associations of the entity to other entities. + * Keys are field names, values object references. * * @var array */ private $_references = array(); /** - * The EntityManager that is responsible for the persistence of the entity. + * The EntityManager that is responsible for the persistent state of the entity. * * @var Doctrine::ORM::EntityManager */ @@ -174,6 +188,14 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * @var integer */ private $_oid; + + /** + * Flag that indicates whether the entity is dirty. + * (which means it has local changes) + * + * @var boolean + */ + //private $_isDirty = false; /** * Constructor. @@ -238,7 +260,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * * Part of the implementation of the Serializable interface. * - * @return array + * @return string */ public function serialize() { @@ -347,19 +369,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable if ($state == null) { return $this->_state; } - - /* TODO: Do we really need this check? This is only for internal use after all. */ - switch ($state) { - 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); - } + $this->_state = $state; } /** @@ -374,39 +384,83 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable /** * Gets the value of a field (regular field or reference). - * If the field is not yet loaded this method does NOT load it. * - * @param $name name of the property - * @throws Doctrine_Entity_Exception if trying to get an unknown field - * @return mixed + * @param $name Name of the field. + * @return mixed Value of the field. + * @throws Doctrine::ORM::Exceptions::EntityException If trying to get an unknown field. */ final protected function _get($fieldName) - { + { + $nullObj = Doctrine_Null::$INSTANCE; if (isset($this->_data[$fieldName])) { - return $this->_internalGetField($fieldName); + return $this->_data[$fieldName] !== $nullObj ? + $this->_data[$fieldName] : null; } else if (isset($this->_references[$fieldName])) { - return $this->_internalGetReference($fieldName); + return $this->_references[$fieldName] !== $nullObj ? + $this->_references[$fieldName] : null; } else { - throw Doctrine_Entity_Exception::unknownField($fieldName); + if ($this->_class->hasField($fieldName)) { + return null; + } else if ($this->_class->hasAssociation($fieldName)) { + $rel = $this->_class->getAssociationMapping($fieldName); + if ($rel->isLazilyFetched()) { + $this->_references[$fieldName] = $rel->lazyLoadFor($this); + return $this->_references[$fieldName] !== $nullObj ? + $this->_references[$fieldName] : null; + } else { + return null; + } + } else { + throw Doctrine_Entity_Exception::invalidField($fieldName); + } } } /** * Sets the value of a field (regular field or reference). - * If the field is not yet loaded this method does NOT load it. * - * @param $name name of the field - * @throws Doctrine_Entity_Exception if trying to get an unknown field - * @return mixed + * @param $fieldName The name of the field. + * @param $value The value of the field. + * @return void + * @throws Doctrine::ORM::Exceptions::EntityException */ final protected function _set($fieldName, $value) { if ($this->_class->hasField($fieldName)) { - return $this->_internalSetField($fieldName, $value); - } else if ($this->_class->hasRelation($fieldName)) { - return $this->_internalSetReference($fieldName, $value); + $old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null; + // NOTE: Common case: $old != $value. Special case: null == 0 (TRUE), which + // is addressed by the type comparison. + if ($old != $value || gettype($old) != gettype($value)) { + $this->_data[$fieldName] = $value; + $this->_dataChangeSet[$fieldName] = array($old => $value); + if ($this->isNew() && $this->_class->isIdentifier($fieldName)) { + $this->_id[$fieldName] = $value; + } + $this->_registerDirty(); + } + } else if ($this->_class->hasAssociation($fieldName)) { + $old = isset($this->_references[$fieldName]) ? $this->_references[$fieldName] : null; + if ($old !== $value) { + $this->_internalSetReference($fieldName, $value); + $this->_referenceChangeSet[$fieldName] = array($old => $value); + $this->_registerDirty(); + if ($old instanceof Doctrine_Collection) { + $this->_em->getUnitOfWork()->scheduleCollectionDeletion($old); + } + if ($value instanceof Doctrine_Collection) { + $this->_em->getUnitOfWork()->scheduleCollectionRecreation($value); + } + } } else { - throw Doctrine_Entity_Exception::unknownField($fieldName); + throw Doctrine_Entity_Exception::invalidField($fieldName); + } + } + + private function _registerDirty() + { + if ($this->_state == self::STATE_MANAGED && + ! $this->_em->getUnitOfWork()->isRegisteredDirty($this)) { + $this->_em->getUnitOfWork()->registerDirty($this); } } @@ -420,7 +474,6 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * * @param string $fieldName * @return mixed - * @todo Rename to _unsafeGetField() */ final public function _internalGetField($fieldName) { @@ -447,6 +500,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** + * INTERNAL: * Gets a reference to another Entity. * * NOTE: Use of this method in userland code is strongly discouraged. @@ -464,16 +518,15 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable /** * INTERNAL: - * Sets a reference to another Entity. + * Sets a reference to another entity or a collection of entities. * * NOTE: Use of this method in userland code is strongly discouraged. * * @param string $fieldName * @param mixed $value - * @todo Refactor. What about composite keys? - * @todo Rename to _unsafeSetReference() + * @todo Refactor. */ - final public function _internalSetReference($name, $value) + final public function _internalSetReference_OLD($name, $value) { if ($value === Doctrine_Null::$INSTANCE) { $this->_references[$name] = $value; @@ -486,7 +539,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable if ($rel instanceof Doctrine_Relation_ForeignKey || $rel instanceof Doctrine_Relation_LocalKey) { if ( ! $rel->isOneToOne()) { - // one-to-many relation found + // one-to-many relation if ( ! $value instanceof Doctrine_Collection) { throw Doctrine_Entity_Exception::invalidValueForOneToManyReference(); } @@ -522,44 +575,71 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable $this->_references[$name] = $value; } + + /** + * INTERNAL: + * Sets a reference to another entity or a collection of entities. + * + * NOTE: Use of this method in userland code is strongly discouraged. + * + * @param string $fieldName + * @param mixed $value + * @todo Refactor. + */ + final public function _internalSetReference($name, $value) + { + if ($value === Doctrine_Null::$INSTANCE) { + $this->_references[$name] = $value; + return; + } + + $rel = $this->_class->getAssociationMapping($name); + + // one-to-many or one-to-one relation + if ($rel->isOneToOne() || $rel->isOneToMany()) { + if ( ! $rel->isOneToOne()) { + // one-to-many relation + if ( ! $value instanceof Doctrine_Collection) { + throw Doctrine_Entity_Exception::invalidValueForOneToManyReference(); + } + if (isset($this->_references[$name])) { + $this->_references[$name]->setData($value->getData()); + return; + } + } else { + $relatedClass = $value->getClass(); + //$foreignFieldName = $rel->getForeignFieldName(); + //$localFieldName = $rel->getLocalFieldName(); + + // one-to-one relation found + if ( ! ($value instanceof Doctrine_Entity)) { + throw Doctrine_Entity_Exception::invalidValueForOneToOneReference(); + } + } + } else if ($rel instanceof Doctrine_Relation_Association) { + if ( ! ($value instanceof Doctrine_Collection)) { + throw Doctrine_Entity_Exception::invalidValueForManyToManyReference(); + } + } + + $this->_references[$name] = $value; + } /** - * Generic getter for all persistent fields. + * Generic getter for all (persistent) fields of the entity. + * + * Invoked by Doctrine::ORM::Access#__get(). * * @param string $fieldName Name of the field. * @return mixed + * @override */ final public function get($fieldName) { if ($getter = $this->_getCustomAccessor($fieldName)) { return $this->$getter(); } - - // Use built-in accessor functionality - $nullObj = Doctrine_Null::$INSTANCE; - if (isset($this->_data[$fieldName])) { - return $this->_data[$fieldName] !== $nullObj ? - $this->_data[$fieldName] : null; - } else if (isset($this->_references[$fieldName])) { - return $this->_references[$fieldName] !== $nullObj ? - $this->_references[$fieldName] : null; - } else { - $class = $this->_class; - if ($class->hasField($fieldName)) { - return null; - } else if ($class->hasRelation($fieldName)) { - $rel = $class->getRelation($fieldName); - if ($rel->isLazilyLoaded()) { - $this->_references[$fieldName] = $rel->lazyLoadFor($this); - return $this->_references[$fieldName] !== $nullObj ? - $this->_references[$fieldName] : null; - } else { - return null; - } - } else { - throw Doctrine_Entity_Exception::invalidField($fieldName); - } - } + return $this->_get($fieldName); } /** @@ -630,42 +710,20 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * Generic setter for persistent fields. + * Generic setter for (persistent) fields of the entity. + * + * Invoked by Doctrine::ORM::Access#__set(). * * @param string $name The name of the field to set. * @param mixed $value The value of the field. + * @override */ final public function set($fieldName, $value) { if ($setter = $this->_getCustomMutator($fieldName)) { return $this->$setter($value); } - - if ($this->_class->hasField($fieldName)) { - /*if ($value instanceof Doctrine_Entity) { - $type = $class->getTypeOf($fieldName); - // FIXME: composite key support - $ids = $value->identifier(); - $id = count($ids) > 0 ? array_pop($ids) : null; - if ($id !== null && $type !== 'object') { - $value = $id; - } - }*/ - - $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; - } - } - } else if ($this->_class->hasRelation($fieldName)) { - $this->_internalSetReference($fieldName, $value); - } else { - throw Doctrine_Entity_Exception::invalidField($fieldName); - } + $this->_set($fieldName, $value); } /** @@ -697,7 +755,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable /** * Clears the value of a field. * - * NOTE: Invoked by Doctrine::ORM::Access#__unset(). + * Invoked by Doctrine::ORM::Access#__unset(). * * @param string $name * @return void @@ -708,7 +766,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable $this->_data[$fieldName] = array(); } else if (isset($this->_references[$fieldName])) { if ($this->_references[$fieldName] instanceof Doctrine_Entity) { - // todo: delete related record when saving $this + // todo: delete related record when saving $this (ONLY WITH deleteOrphans!) $this->_references[$fieldName] = Doctrine_Null::$INSTANCE; } else if ($this->_references[$fieldName] instanceof Doctrine_Collection) { $this->_references[$fieldName]->setData(array()); @@ -722,72 +780,14 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * * @return array */ - final public function _getChangeSet() + final public function _getDataChangeSet() { - //return $this->_changeSet; + return $this->_dataChangeSet; } - - /** - * Returns an array of modified fields and values with data preparation - * adds column aggregation inheritance and converts Records into primary key values - * - * @param array $array - * @return array - * @todo What about a little bit more expressive name? getPreparedData? - * @todo Does not look like the best place here ... - * @todo Prop: Move to EntityPersister. There call _getChangeSet() and apply this logic. - */ - final public function getPrepared(array $array = array()) + + final public function _getReferenceChangeSet() { - $dataSet = array(); - - if (empty($array)) { - $modifiedFields = $this->_modified; - } - - foreach ($modifiedFields as $field) { - $type = $this->_class->getTypeOfField($field); - - if ($this->_data[$field] === Doctrine_Null::$INSTANCE) { - $dataSet[$field] = null; - continue; - } - - switch ($type) { - case 'array': - case 'object': - $dataSet[$field] = serialize($this->_data[$field]); - break; - case 'gzip': - $dataSet[$field] = gzcompress($this->_data[$field],5); - break; - case 'boolean': - $dataSet[$field] = $this->_em->getConnection() - ->convertBooleans($this->_data[$field]); - break; - case 'enum': - $dataSet[$field] = $this->_class->enumIndex($field, $this->_data[$field]); - break; - default: - $dataSet[$field] = $this->_data[$field]; - } - } - - // @todo cleanup - // populates the discriminator field in Single & Class Table Inheritance - if ($this->_class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED || - $this->_class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) { - $discCol = $this->_class->getInheritanceOption('discriminatorColumn'); - $discMap = $this->_class->getInheritanceOption('discriminatorMap'); - $old = $this->get($discCol, false); - $discValue = array_search($this->_entityName, $discMap); - if ((string) $old !== (string) $discValue || $old === null) { - $dataSet[$discCol] = $discValue; - $this->_data[$discCol] = $discValue; - } - } - - return $dataSet; + return $this->_referenceChangeSet; } /** @@ -808,7 +808,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable */ final public function isModified() { - return count($this->_modified) > 0; + return count($this->_fieldChangeSet) > 0; } /** @@ -830,7 +830,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable $this->_id[$name] = $id; $this->_data[$name] = $id; } - $this->_modified = array(); + $this->_dataChangeSet = array(); } /** @@ -948,6 +948,8 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * from the instance pool. * Note: The entity is no longer useable after free() has been called. Any operations * done with the entity afterwards can lead to unpredictable results. + * + * @param boolean $deep Whether to cascade the free() call to (loaded) associated entities. */ public function free($deep = false) { @@ -967,4 +969,107 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable $this->_references = array(); } } + + /** + * Check if an offsetExists. + * + * Part of the ArrayAccess implementation. + * + * @param mixed $offset + * @return boolean whether or not this object contains $offset + */ + public function offsetExists($offset) + { + return $this->contains($offset); + } + + /** + * offsetGet an alias of get() + * + * Part of the ArrayAccess implementation. + * + * @see get, __get + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Part of the ArrayAccess implementation. + * + * sets $offset to $value + * @see set, __set + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + return $this->set($offset, $value); + } + + /** + * Part of the ArrayAccess implementation. + * + * unset a given offset + * @see set, offsetSet, __set + * @param mixed $offset + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + /** + * __set + * + * @see set, offsetSet + * @param $name + * @param $value + * @since 1.0 + * @return void + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * __get + * + * @see get, offsetGet + * @param mixed $name + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * __isset() + * + * @param string $name + * @since 1.0 + * @return boolean whether or not this object contains $name + */ + public function __isset($name) + { + return $this->contains($name); + } + + /** + * __unset() + * + * @param string $name + * @since 1.0 + * @return void + */ + public function __unset($name) + { + return $this->remove($name); + } } diff --git a/lib/Doctrine/EntityManager.php b/lib/Doctrine/EntityManager.php index 98cd71511..d3f8610ef 100644 --- a/lib/Doctrine/EntityManager.php +++ b/lib/Doctrine/EntityManager.php @@ -29,7 +29,6 @@ #use Doctrine::ORM::Internal::UnitOfWork; #use Doctrine::ORM::Mapping::ClassMetadata; - /** * The EntityManager is the central access point to ORM functionality. * @@ -38,12 +37,28 @@ * @since 2.0 * @version $Revision$ * @author Roman Borschel - * @todo package:orm */ class Doctrine_EntityManager { + /** + * IMMEDIATE: Flush occurs automatically after each operation that issues database + * queries. No operations are queued. + */ + const FLUSHMODE_IMMEDIATE = 'immediated'; + /** + * AUTO: Flush occurs automatically in the following situations: + * - Before any query executions (to prevent getting stale data) + * - On EntityManager#commit() + */ const FLUSHMODE_AUTO = 'auto'; + /** + * COMMIT: Flush occurs automatically only on EntityManager#commit(). + */ const FLUSHMODE_COMMIT = 'commit'; + /** + * MANUAL: Flush occurs never automatically. The only way to flush is + * through EntityManager#flush(). + */ const FLUSHMODE_MANUAL = 'manual'; /** @@ -68,19 +83,6 @@ class Doctrine_EntityManager */ private $_conn; - /** - * Flush modes enumeration. - */ - private static $_flushModes = array( - // auto: Flush occurs automatically after each operation that issues database - // queries. No operations are queued. - self::FLUSHMODE_AUTO, - // commit: Flush occurs automatically at transaction commit. - self::FLUSHMODE_COMMIT, - // manual: Flush occurs never automatically. - self::FLUSHMODE_MANUAL - ); - /** * The metadata factory, used to retrieve the metadata of entity classes. * @@ -160,7 +162,6 @@ class Doctrine_EntityManager * Gets the metadata for a class. Alias for getClassMetadata(). * * @return Doctrine_Metadata - * @todo package:orm */ public function getMetadata($className) { @@ -324,12 +325,20 @@ class Doctrine_EntityManager */ public function setFlushMode($flushMode) { - if ( ! in_array($flushMode, self::$_flushModes)) { + if ( ! $this->_isFlushMode($flushMode)) { throw Doctrine_EntityManager_Exception::invalidFlushMode(); } $this->_flushMode = $flushMode; } + private function _isFlushMode($value) + { + return $value == self::FLUSHMODE_AUTO || + $value == self::FLUSHMODE_COMMIT || + $value == self::FLUSHMODE_IMMEDIATE || + $value == self::FLUSHMODE_MANUAL; + } + /** * Gets the currently used flush mode. * @@ -365,10 +374,9 @@ class Doctrine_EntityManager } /** - * getResultCacheDriver + * Gets the result cache driver used by the EntityManager. * - * @return Doctrine_Cache_Interface - * @todo package:orm + * @return Doctrine::ORM::Cache::CacheDriver The cache driver. */ public function getResultCacheDriver() { @@ -383,7 +391,6 @@ class Doctrine_EntityManager * getQueryCacheDriver * * @return Doctrine_Cache_Interface - * @todo package:orm */ public function getQueryCacheDriver() { @@ -396,31 +403,45 @@ class Doctrine_EntityManager /** * Saves the given entity, persisting it's state. + * + * @param Doctrine::ORM::Entity $entity + * @return void */ public function save(Doctrine_Entity $entity) { $this->_unitOfWork->save($entity); - if ($this->_flushMode == self::FLUSHMODE_AUTO) { + if ($this->_flushMode == self::FLUSHMODE_IMMEDIATE) { $this->flush(); } } /** * Removes the given entity from the persistent store. + * + * @param Doctrine::ORM::Entity $entity + * @return void */ public function delete(Doctrine_Entity $entity) { $this->_unitOfWork->delete($entity); + if ($this->_flushMode == self::FLUSHMODE_IMMEDIATE) { + $this->flush(); + } } /** - * Refreshes the persistent state of the entity from the database. + * Refreshes the persistent state of the entity from the database, + * overriding any local changes that have not yet been persisted. * - * @param Doctrine_Entity $entity + * @param Doctrine::ORM::Entity $entity + * @return void + * @todo FIX Impl */ public function refresh(Doctrine_Entity $entity) { - //... + $this->_mergeData($entity, $entity->getRepository()->find( + $entity->identifier(), Doctrine_Query::HYDRATE_ARRAY), + true); } /** @@ -463,7 +484,7 @@ class Doctrine_EntityManager * * @param string $className The name of the entity class. * @param array $data The data for the entity. - * @return Doctrine_Entity + * @return Doctrine::ORM::Entity */ public function createEntity($className, array $data) { @@ -488,6 +509,7 @@ class Doctrine_EntityManager $idHash = $this->_unitOfWork->getIdentifierHash($id); if ($entity = $this->_unitOfWork->tryGetByIdHash($idHash, $classMetadata->getRootClassName())) { + $this->_mergeData($entity, $data); return $entity; } else { $entity = new $className; @@ -501,10 +523,35 @@ class Doctrine_EntityManager return $entity; } + /** + * Merges the given data into the given entity, optionally overriding + * local changes. + * + * @param Doctrine::ORM::Entity $entity + * @param array $data + * @param boolean $overrideLocalChanges + * @return void + */ + private function _mergeData(Doctrine_Entity $entity, array $data, $overrideLocalChanges = false) { + if ($overrideLocalChanges) { + foreach ($data as $field => $value) { + $entity->_internalSetField($field, $value); + } + } else { + foreach ($data as $field => $value) { + if ( ! $entity->contains($field) || $entity->_internalGetField($field) === null) { + $entity->_internalSetField($field, $value); + } + } + } + } + /** * Checks if the instance is managed by the EntityManager. * - * @return boolean + * @param Doctrine::ORM::Entity $entity + * @return boolean TRUE if this EntityManager currently manages the given entity + * (and has it in the identity map), FALSE otherwise. */ public function contains(Doctrine_Entity $entity) { @@ -513,8 +560,11 @@ class Doctrine_EntityManager } /** - * INTERNAL: - * For internal hydration purposes only. + * INTERNAL: For internal hydration purposes only. + * + * Gets the temporarily stored entity data. + * + * @return array */ public function _getTmpEntityData() { @@ -528,6 +578,8 @@ class Doctrine_EntityManager * class to instantiate. If no discriminator column is found, the given * classname will be returned. * + * @param array $data + * @param string $className * @return string The name of the class to instantiate. */ private function _inferCorrectClassName(array $data, $className) @@ -562,6 +614,7 @@ class Doctrine_EntityManager * Sets the EventManager used by the EntityManager. * * @param Doctrine::Common::EventManager $eventManager + * @return void */ public function setEventManager(Doctrine_EventManager $eventManager) { @@ -572,6 +625,7 @@ class Doctrine_EntityManager * Sets the Configuration used by the EntityManager. * * @param Doctrine::Common::Configuration $config + * @return void */ public function setConfiguration(Doctrine_Configuration $config) { diff --git a/lib/Doctrine/EntityPersister/Abstract.php b/lib/Doctrine/EntityPersister/Abstract.php index 38006c61a..818495947 100644 --- a/lib/Doctrine/EntityPersister/Abstract.php +++ b/lib/Doctrine/EntityPersister/Abstract.php @@ -55,7 +55,7 @@ abstract class Doctrine_EntityPersister_Abstract /** * The Doctrine_Connection object that the database connection of this mapper. * - * @var Doctrine_Connection $conn + * @var Doctrine::DBAL::Connection $conn */ protected $_conn; @@ -85,17 +85,58 @@ abstract class Doctrine_EntityPersister_Abstract public function insert(Doctrine_Entity $entity) { - //... + $insertData = array(); + $dataChangeSet = $entity->_getDataChangeSet(); + $referenceChangeSet = $entity->_getReferenceChangeSet(); + + foreach ($referenceChangeSet as $field => $change) { + list($old, $new) = each($change); + $assocMapping = $entity->getClass()->getAssociationMapping($field); + if ($assocMapping->isOneToOne()) { + if ($assocMapping->isInverseSide()) { + //echo "INVERSE!"; + continue; // ignore inverse side + } + foreach ($assocMapping->getSourceToTargetKeyColumns() as $localColumn => $foreignColumn) { + //echo "$localColumn -- $foreignColumn
"; + //$insertData[$localColumn] = 1; //FIX + } + // ... set the foreign key column to the id of the related entity ($new) + } + //... + } + + foreach ($dataChangeSet as $field => $change) { + $insertData[$entity->getClass()->getColumnName($field)] = current($change); + } + + //TODO: perform insert + $this->_conn->insert($entity->getClass()->getTableName(), $insertData); } public function update(Doctrine_Entity $entity) { - //... + $dataChangeSet = $entity->_getDataChangeSet(); + $referenceChangeSet = $entity->_getReferenceChangeSet(); + + foreach ($referenceChangeSet as $field => $change) { + $assocMapping = $entity->getClass()->getAssociationMapping($field); + if ($assocMapping instanceof Doctrine_Association_OneToOneMapping) { + if ($assocMapping->isInverseSide()) { + continue; // ignore inverse side + } + // ... null out the foreign key + + } + //... + } + + //TODO: perform update } public function delete(Doctrine_Entity $entity) { - + //TODO: perform delete } /** @@ -182,6 +223,64 @@ abstract class Doctrine_EntityPersister_Abstract return $converted; } + /** + * Returns an array of modified fields and values with data preparation + * adds column aggregation inheritance and converts Records into primary key values + * + * @param array $array + * @return array + * @todo Move to EntityPersister. There call _getChangeSet() and apply this logic. + */ + public function prepareData(array $data = array()) + { + $dataSet = array(); + + $modifiedFields = $fields; + + foreach ($data as $field => $value) { + $type = $this->_classMetadata->getTypeOfField($field); + + if ($value === Doctrine_Null::$INSTANCE) { + $dataSet[$field] = null; + continue; + } + + switch ($type) { + case 'array': + case 'object': + $dataSet[$field] = serialize($value); + break; + case 'gzip': + $dataSet[$field] = gzcompress($value, 5); + break; + case 'boolean': + $dataSet[$field] = $this->_em->getConnection() + ->convertBooleans($value); + break; + case 'enum': + $dataSet[$field] = $this->_class->enumIndex($field, $value); + break; + default: + $dataSet[$field] = $value; + } + } + + // @todo cleanup + // populates the discriminator field in Single & Class Table Inheritance + if ($this->_classMetadata->getInheritanceType() == Doctrine_ClassMetadata::INHERITANCE_TYPE_JOINED || + $this->_class->getInheritanceType() == Doctrine_ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE) { + $discCol = $this->_classMetadata->getInheritanceOption('discriminatorColumn'); + $discMap = $this->_classMetadata->getInheritanceOption('discriminatorMap'); + $old = $this->get($discCol, false); + $discValue = array_search($this->_entityName, $discMap); + if ((string) $old !== (string) $discValue || $old === null) { + $dataSet[$discCol] = $discValue; + //$this->_data[$discCol] = $discValue; + } + } + + return $dataSet; + } @@ -226,75 +325,6 @@ abstract class Doctrine_EntityPersister_Abstract return $this->_em; } - /** - * prepareValue - * this method performs special data preparation depending on - * the type of the given column - * - * 1. It unserializes array and object typed columns - * 2. Uncompresses gzip typed columns - * 3. Gets the appropriate enum values for enum typed columns - * 4. Initializes special null object pointer for null values (for fast column existence checking purposes) - * - * example: - * - * $field = 'name'; - * $value = null; - * $table->prepareValue($field, $value); // Doctrine_Null - * - * - * @throws Doctrine_Table_Exception if unserialization of array/object typed column fails or - * @throws Doctrine_Table_Exception if uncompression of gzip typed column fails * - * @param string $field the name of the field - * @param string $value field value - * @param string $typeHint A hint on the type of the value. If provided, the type lookup - * for the field can be skipped. Used i.e. during hydration to - * improve performance on large and/or complex results. - * @return mixed prepared value - * @todo To EntityManager. Make private and use in createEntity(). - * .. Or, maybe better: Move to hydrator for performance reasons. - */ - /*public function prepareValue($fieldName, $value, $typeHint = null) - { - if ($value === $this->_nullObject) { - return $this->_nullObject; - } else if ($value === null) { - return null; - } else { - $type = is_null($typeHint) ? $this->_classMetadata->getTypeOf($fieldName) : $typeHint; - switch ($type) { - case 'integer': - case 'string'; - // don't do any casting here PHP INT_MAX is smaller than what the databases support - break; - case 'enum': - return $this->_classMetadata->enumValue($fieldName, $value); - break; - case 'boolean': - return (boolean) $value; - break; - case 'array': - case 'object': - if (is_string($value)) { - $value = unserialize($value); - if ($value === false) { - throw new Doctrine_Mapper_Exception('Unserialization of ' . $fieldName . ' failed.'); - } - return $value; - } - break; - case 'gzip': - $value = gzuncompress($value); - if ($value === false) { - throw new Doctrine_Mapper_Exception('Uncompressing of ' . $fieldName . ' failed.'); - } - return $value; - break; - } - } - return $value; - }*/ - /** * getComponentName * diff --git a/lib/Doctrine/EntityPersister/Standard.php b/lib/Doctrine/EntityPersister/Standard.php index da20b2326..67c315c3c 100644 --- a/lib/Doctrine/EntityPersister/Standard.php +++ b/lib/Doctrine/EntityPersister/Standard.php @@ -24,8 +24,6 @@ * as is the case in Single Table Inheritance & Concrete Table Inheritance. * * @author Roman Borschel - * @package Doctrine - * @subpackage Abstract * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ * @link www.phpdoctrine.org @@ -72,7 +70,6 @@ class Doctrine_EntityPersister_Standard extends Doctrine_EntityPersister_Abstrac return false; } - //$class = $record->getClassMetadata(); $class = $this->_classMetadata; $identifier = $class->getIdentifier(); $fields = $this->_convertFieldToColumnNames($fields, $class); diff --git a/lib/Doctrine/EventManager.php b/lib/Doctrine/EventManager.php index 46ec435a3..6c2b3dfb0 100644 --- a/lib/Doctrine/EventManager.php +++ b/lib/Doctrine/EventManager.php @@ -19,11 +19,11 @@ * . */ -#namespace Doctrine::Common; +#namespace Doctrine::Common::Event; /** * The EventManager is the central point of Doctrine's event listener system. - * Listeners are registered on the manager and events are dispatch through the + * Listeners are registered on the manager and events are dispatched through the * manager. * * @author Roman Borschel @@ -100,6 +100,17 @@ class Doctrine_EventManager $this->_listeners[$event] = $listener; } } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param Doctrine::Common::Event::EventSubscriber $subscriber The subscriber. + */ + public function addEventSubscriber(Doctrine_EventSubscriber $subscriber) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); + } } ?> \ No newline at end of file diff --git a/lib/Doctrine/Exception.php b/lib/Doctrine/Exception.php index e175073b0..a5b356586 100644 --- a/lib/Doctrine/Exception.php +++ b/lib/Doctrine/Exception.php @@ -49,7 +49,7 @@ class Doctrine_Exception extends Exception return $this->_innerException; } - public static function notImplemented($method, $class) + public static function notYetImplemented($method, $class) { return new self("The method '$method' is not implemented in the class '$class'."); } diff --git a/lib/Doctrine/Hydrator/RecordDriver.php b/lib/Doctrine/Hydrator/RecordDriver.php index 9b425932a..5b4155eb0 100644 --- a/lib/Doctrine/Hydrator/RecordDriver.php +++ b/lib/Doctrine/Hydrator/RecordDriver.php @@ -70,8 +70,8 @@ class Doctrine_Hydrator_RecordDriver public function initRelatedCollection(Doctrine_Entity $entity, $name) { if ( ! isset($this->_initializedRelations[$entity->getOid()][$name])) { - $relation = $entity->getClass()->getRelation($name); - $relatedClass = $relation->getTable(); + $relation = $entity->getClass()->getAssociationMapping($name); + $relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); $coll = $this->getElementCollection($relatedClass->getClassName()); $coll->setReference($entity, $relation); $entity->_internalSetReference($name, $coll); diff --git a/lib/Doctrine/HydratorNew.php b/lib/Doctrine/HydratorNew.php index 664fcf6d9..6facb152e 100644 --- a/lib/Doctrine/HydratorNew.php +++ b/lib/Doctrine/HydratorNew.php @@ -49,8 +49,6 @@ * That's why the performance of the _gatherRowData() methods which are responsible * for the "numRowsInResult * numColumnsInResult" part is crucial to fast hydration. * - * @package Doctrine - * @subpackage Hydrator * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 @@ -212,7 +210,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $entityName = $map['metadata']->getClassName(); $parent = $map['parent']; $relation = $map['relation']; - $relationAlias = $relation->getAlias(); + $relationAlias = $relation->getSourceFieldName();//$relation->getAlias(); $path = $parent . '.' . $dqlAlias; // pick the right element that will get the associated element attached diff --git a/lib/Doctrine/MappingException.php b/lib/Doctrine/MappingException.php index 05650a4ee..902c65a00 100644 --- a/lib/Doctrine/MappingException.php +++ b/lib/Doctrine/MappingException.php @@ -9,8 +9,8 @@ class Doctrine_MappingException extends Doctrine_Exception { public static function identifierRequired($entityName) { - return new self("No identifier specified for Entity '$entityName'." - . " Every Entity must have an identifier."); + return new self("No identifier/primary key specified for Entity '$entityName'." + . " Every Entity must have an identifier/primary key."); } public static function invalidInheritanceType($type) @@ -28,6 +28,25 @@ class Doctrine_MappingException extends Doctrine_Exception return new self("Id generators can't be used with a composite id."); } + public static function missingFieldName() + { + return new self("The association mapping misses the 'fieldName' attribute."); + } + + public static function missingTargetEntity($fieldName) + { + return new self("The association mapping '$fieldName' misses the 'targetEntity' attribute."); + } + + public static function missingSourceEntity($fieldName) + { + return new self("The association mapping '$fieldName' misses the 'sourceEntity' attribute."); + } + + public static function mappingNotFound($fieldName) + { + return new self("No mapping found for field '$fieldName'."); + } } ?> \ No newline at end of file diff --git a/lib/Doctrine/Query/CacheHandler.php b/lib/Doctrine/Query/CacheHandler.php index 1e0e7bc2e..5c977d7a7 100755 --- a/lib/Doctrine/Query/CacheHandler.php +++ b/lib/Doctrine/Query/CacheHandler.php @@ -20,10 +20,6 @@ * . */ -Doctrine::autoload('Doctrine_Query_AbstractResult'); -Doctrine::autoload('Doctrine_Query_ParserResult'); -Doctrine::autoload('Doctrine_Query_QueryResult'); - /** * Doctrine_Query_CacheHandler * @@ -136,8 +132,8 @@ abstract class Doctrine_Query_CacheHandler $queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable(); } else { $queryComponents[$alias]['parent'] = $e[0]; - $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]); - $queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($queryComponents[$alias]['relation']->getForeignComponentName()); + $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getAssociation($e[1]); + $queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($queryComponents[$alias]['relation']->getTargetEntityName()); $queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable(); } diff --git a/lib/Doctrine/Query/Production/Join.php b/lib/Doctrine/Query/Production/Join.php index 0967dc83f..2280cb4fb 100644 --- a/lib/Doctrine/Query/Production/Join.php +++ b/lib/Doctrine/Query/Production/Join.php @@ -50,7 +50,7 @@ class Doctrine_Query_Production_Join extends Doctrine_Query_Production $this->_parser->match(Doctrine_Query_Token::T_LEFT); $this->_joinType = 'LEFT'; - } elseif ($this->_isNextToken(Doctrine_Query_Token::T_INNER)) { + } else if ($this->_isNextToken(Doctrine_Query_Token::T_INNER)) { $this->_parser->match(Doctrine_Query_Token::T_INNER); } @@ -64,7 +64,7 @@ class Doctrine_Query_Production_Join extends Doctrine_Query_Production $this->_whereType = 'ON'; $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); - } elseif ($this->_isNextToken(Doctrine_Query_Token::T_WITH)) { + } else if ($this->_isNextToken(Doctrine_Query_Token::T_WITH)) { $this->_parser->match(Doctrine_Query_Token::T_WITH); $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); diff --git a/lib/Doctrine/Query/Production/PathExpression.php b/lib/Doctrine/Query/Production/PathExpression.php index c49178157..1f8e4b7e2 100644 --- a/lib/Doctrine/Query/Production/PathExpression.php +++ b/lib/Doctrine/Query/Production/PathExpression.php @@ -93,7 +93,7 @@ class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production $relationName = $this->_identifiers[$i]; $path .= '.' . $relationName; - if ( ! $classMetadata->hasRelation($relationName)) { + if ( ! $classMetadata->hasAssociation($relationName)) { $className = $classMetadata->getClassName(); $this->_parser->semanticalError( diff --git a/lib/Doctrine/Query/Production/PathExpressionEndingWithAsterisk.php b/lib/Doctrine/Query/Production/PathExpressionEndingWithAsterisk.php index c92662df0..48b208530 100644 --- a/lib/Doctrine/Query/Production/PathExpressionEndingWithAsterisk.php +++ b/lib/Doctrine/Query/Production/PathExpressionEndingWithAsterisk.php @@ -73,7 +73,7 @@ class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrin $relationName = $this->_identifiers[$i]; $path .= '.' . $relationName; - if ( ! $classMetadata->hasRelation($relationName)) { + if ( ! $classMetadata->hasAssociation($relationName)) { $className = $classMetadata->getClassName(); $this->_parser->semanticalError( diff --git a/lib/Doctrine/Query/Production/RangeVariableDeclaration.php b/lib/Doctrine/Query/Production/RangeVariableDeclaration.php index f8ca652b7..5ca043d81 100644 --- a/lib/Doctrine/Query/Production/RangeVariableDeclaration.php +++ b/lib/Doctrine/Query/Production/RangeVariableDeclaration.php @@ -191,7 +191,7 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_ } } else { // We don't have the query component yet - if ( ! $classMetadata->hasRelation($relationName)) { + if ( ! $classMetadata->hasAssociation($relationName)) { $className = $classMetadata->getClassName(); $this->_parser->semanticalError("Relation '{$relationName}' does not exist in component '{$className}'"); @@ -199,13 +199,13 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_ return; } - // Retrieving ClassMetadata and Mapper + // Retrieving ClassMetadata try { - $relation = $classMetadata->getRelation($relationName); - $classMetadata = $relation->getClassMetadata(); + $relation = $classMetadata->getAssociationMapping($relationName); + $targetClassMetadata = $this->_em->getClassMetadata($relation->getTargetEntityName()); $queryComponent = array( - 'metadata' => $classMetadata, + 'metadata' => $targetClassMetadata, 'parent' => $parent, 'relation' => $relation, 'map' => null, @@ -226,7 +226,7 @@ class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_ $this->_identificationVariable = $path; } - $tableAlias = $parserResult->generateTableAlias($classMetadata->getClassName()); + $tableAlias = $parserResult->generateTableAlias($targetClassMetadata->getClassName()); //echo "Table alias: " . $tableAlias . "\n"; diff --git a/lib/Doctrine/Relation.php b/lib/Doctrine/Relation.php index 0b5bddcec..84d6cb509 100644 --- a/lib/Doctrine/Relation.php +++ b/lib/Doctrine/Relation.php @@ -154,7 +154,7 @@ abstract class Doctrine_Relation implements ArrayAccess } } $this->definition = $def; - $this->_foreignMapper = $this->getTable()->getConnection()->getEntityPersister($def['class']); + $this->_foreignMapper = $this->getTable()->getEntityManager()->getEntityPersister($def['class']); } /** diff --git a/lib/Doctrine/Relation/Parser.php b/lib/Doctrine/Relation/Parser.php index d2bb39364..7332ab3ac 100644 --- a/lib/Doctrine/Relation/Parser.php +++ b/lib/Doctrine/Relation/Parser.php @@ -410,7 +410,7 @@ class Doctrine_Relation_Parser */ public function completeDefinition($def) { - $conn = $this->_table->getConnection(); + $conn = $this->_table->getEntityManager(); $def['table'] = $this->getImpl($def, 'class'); $def['localTable'] = $this->_table; diff --git a/lib/Doctrine/Schema/MsSqlSchemaManager.php b/lib/Doctrine/Schema/MsSqlSchemaManager.php index 412bc386c..08d72a6a3 100644 --- a/lib/Doctrine/Schema/MsSqlSchemaManager.php +++ b/lib/Doctrine/Schema/MsSqlSchemaManager.php @@ -187,7 +187,7 @@ class Doctrine_Schema_MsSqlSchemaManager extends Doctrine_Schema_SchemaManager if ($query) { $query .= ', '; } - $query .= 'ADD ' . $this->conn->getDeclaration($fieldName, $field); + $query .= 'ADD ' . $this->getDeclaration($fieldName, $field); } } diff --git a/tests/Orm/AllTests.php b/tests/Orm/AllTests.php index c8746fc14..a212b6a68 100644 --- a/tests/Orm/AllTests.php +++ b/tests/Orm/AllTests.php @@ -15,6 +15,8 @@ require_once 'Orm/Entity/AllTests.php'; // Tests require_once 'Orm/UnitOfWorkTest.php'; require_once 'Orm/EntityManagerFactoryTest.php'; +require_once 'Orm/EntityManagerTest.php'; +require_once 'Orm/EntityPersisterTest.php'; class Orm_AllTests { @@ -29,7 +31,8 @@ class Orm_AllTests $suite->addTestSuite('Orm_UnitOfWorkTest'); $suite->addTestSuite('Orm_EntityManagerFactoryTest'); - //$suite->addTestSuite('Orm_ConfigurableTestCase'); + $suite->addTestSuite('Orm_EntityManagerTest'); + $suite->addTestSuite('Orm_EntityPersisterTest'); $suite->addTest(Orm_Component_AllTests::suite()); $suite->addTest(Orm_Query_AllTests::suite()); diff --git a/tests/Orm/Associations/OneToOneMappingTest.php b/tests/Orm/Associations/OneToOneMappingTest.php index 62f81a1cb..2861fd818 100644 --- a/tests/Orm/Associations/OneToOneMappingTest.php +++ b/tests/Orm/Associations/OneToOneMappingTest.php @@ -9,7 +9,7 @@ class Orm_Associations_OneToOneMappingTest extends Doctrine_OrmTestCase 'fieldName' => 'address', 'targetEntity' => 'Address', 'joinColumns' => array('address_id' => 'id'), - 'sourceEntity' => 'Person' // This is normally filled by ClassMetadata + 'sourceEntity' => 'Person', // This is normally filled by ClassMetadata ); $oneToOneMapping = new Doctrine_Association_OneToOne($owningSideMapping); @@ -23,11 +23,16 @@ class Orm_Associations_OneToOneMappingTest extends Doctrine_OrmTestCase $inverseSideMapping = array( + 'fieldName' => 'person', + 'sourceEntity' => 'Address', + 'targetEntity' => 'Person', 'mappedBy' => 'address' ); $oneToOneMapping = new Doctrine_Association_OneToOne($inverseSideMapping); $this->assertEquals('address', $oneToOneMapping->getMappedByFieldName()); + $this->assertEquals('Address', $oneToOneMapping->getSourceEntityName()); + $this->assertEquals('Person', $oneToOneMapping->getTargetEntityName()); $this->assertTrue($oneToOneMapping->isInverseSide()); } diff --git a/tests/Orm/Component/AccessTest.php b/tests/Orm/Component/AccessTest.php index 054feaf67..27efc91b6 100644 --- a/tests/Orm/Component/AccessTest.php +++ b/tests/Orm/Component/AccessTest.php @@ -26,7 +26,7 @@ * @author Bjarte Stien Karlsen * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org - * @since 1.0 + * @since 2.0 * @version $Revision: 3754 $ */ require_once 'lib/DoctrineTestInit.php'; @@ -95,23 +95,6 @@ class Orm_Component_AccessTest extends Doctrine_OrmTestCase } - /** - * @test - */ - public function shouldSetArrayOfValusInRecord() - { - $this->user->setArray(array( - 'username' => 'meus', - 'id' => 22)); - - $this->assertEquals('meus', $this->user->username); - $this->assertEquals('meus', $this->user['username']); - - $this->assertEquals(22, $this->user->id); - $this->assertEquals(22, $this->user['id']); - } - - /** * @test * @expectedException Doctrine_Entity_Exception @@ -130,17 +113,6 @@ class Orm_Component_AccessTest extends Doctrine_OrmTestCase $this->user['rat'] = 'meus'; } - /** - * @test - * @expectedException Doctrine_Entity_Exception - */ - public function shouldNotBeAbleToSetNonExistantFieldAsPartInSetArray() - { - $this->user->setArray(array( - 'rat' => 'meus', - 'id' => 22)); - } - /** * @test @@ -176,18 +148,6 @@ class Orm_Component_AccessTest extends Doctrine_OrmTestCase $this->assertFalse(isset($col->test)); } - - /** - * - * @test - * @expectedException Doctrine_Exception - */ - public function shouldNotBeAbleToSetNullFieldInRecord() - { - $this->user->offsetSet(null, 'test'); - - } - /** * @test * @expectedException Doctrine_Exception diff --git a/tests/Orm/EntityManagerTest.php b/tests/Orm/EntityManagerTest.php new file mode 100644 index 000000000..9509db18a --- /dev/null +++ b/tests/Orm/EntityManagerTest.php @@ -0,0 +1,20 @@ +_em->getFlushMode(); + try { + $this->_em->setFlushMode('foobar'); + $this->fail("Setting invalid flushmode did not trigger exception."); + } catch (Doctrine_EntityManager_Exception $expected) {} + $this->_em->setFlushMode($prev); + } +} \ No newline at end of file diff --git a/tests/Orm/EntityPersisterTest.php b/tests/Orm/EntityPersisterTest.php new file mode 100644 index 000000000..cfff88c57 --- /dev/null +++ b/tests/Orm/EntityPersisterTest.php @@ -0,0 +1,42 @@ +_connMock = new Doctrine_ConnectionMock(array()); + $this->_emMock = new Doctrine_EntityManagerMock($this->_connMock); + $this->_connMock->setDatabasePlatform(new Doctrine_DatabasePlatformMock()); + + $this->_persister = new Doctrine_EntityPersister_Standard( + $this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); + } + + public function testTest() { + $user = new ForumUser(); + $user->username = "romanb"; + + $user->avatar = new ForumAvatar(); + + $this->_persister->insert($user); + + $inserts = $this->_connMock->getInserts(); + //var_dump($inserts); + $this->assertTrue(isset($inserts['forum_user'])); + $this->assertEquals(1, count($inserts['forum_user'])); + $this->assertEquals(1, count($inserts['forum_user'][0])); + $this->assertTrue(isset($inserts['forum_user'][0]['username'])); + $this->assertEquals('romanb', $inserts['forum_user'][0]['username']); + } + +} \ No newline at end of file diff --git a/tests/Orm/Hydration/BasicHydrationTest.php b/tests/Orm/Hydration/BasicHydrationTest.php index 38411990d..179a55d30 100644 --- a/tests/Orm/Hydration/BasicHydrationTest.php +++ b/tests/Orm/Hydration/BasicHydrationTest.php @@ -123,7 +123,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'p' => array( 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('phonenumbers'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), 'map' => null ) ); @@ -226,7 +226,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'p' => array( 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('phonenumbers'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), 'map' => null, 'agg' => array('0' => 'numPhones') ) @@ -311,7 +311,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'p' => array( 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('phonenumbers'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), 'map' => 'phonenumber' ) ); @@ -415,13 +415,13 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'p' => array( 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('phonenumbers'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), 'map' => null ), 'a' => array( 'metadata' => $this->_em->getClassMetadata('CmsArticle'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('articles'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('articles'), 'map' => null ), ); @@ -571,19 +571,19 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'p' => array( 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('phonenumbers'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), 'map' => null ), 'a' => array( 'metadata' => $this->_em->getClassMetadata('CmsArticle'), 'parent' => 'u', - 'relation' => $this->_em->getClassMetadata('CmsUser')->getRelation('articles'), + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('articles'), 'map' => null ), 'c' => array( 'metadata' => $this->_em->getClassMetadata('CmsComment'), 'parent' => 'a', - 'relation' => $this->_em->getClassMetadata('CmsArticle')->getRelation('comments'), + 'relation' => $this->_em->getClassMetadata('CmsArticle')->getAssociationMapping('comments'), 'map' => null ), ); @@ -779,7 +779,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'b' => array( 'metadata' => $this->_em->getClassMetadata('ForumBoard'), 'parent' => 'c', - 'relation' => $this->_em->getClassMetadata('ForumCategory')->getRelation('boards'), + 'relation' => $this->_em->getClassMetadata('ForumCategory')->getAssociationMapping('boards'), 'map' => null ), ); diff --git a/tests/Orm/Query/IdentifierRecognitionTest.php b/tests/Orm/Query/IdentifierRecognitionTest.php index c12b290d8..604de9692 100755 --- a/tests/Orm/Query/IdentifierRecognitionTest.php +++ b/tests/Orm/Query/IdentifierRecognitionTest.php @@ -84,7 +84,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase $decl = $parserResult->getQueryComponent('p'); $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Relation); + $this->assertTrue($decl['relation'] instanceof Doctrine_Association); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals(null, $decl['map']); @@ -108,7 +108,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase $decl = $parserResult->getQueryComponent('a'); $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Relation); + $this->assertTrue($decl['relation'] instanceof Doctrine_Association); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals(null, $decl['map']); @@ -116,7 +116,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase $decl = $parserResult->getQueryComponent('pn'); $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Relation); + $this->assertTrue($decl['relation'] instanceof Doctrine_Association); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals('phonenumber', $decl['map']); diff --git a/tests/lib/mocks/Doctrine_ConnectionMock.php b/tests/lib/mocks/Doctrine_ConnectionMock.php index 864455896..763309677 100644 --- a/tests/lib/mocks/Doctrine_ConnectionMock.php +++ b/tests/lib/mocks/Doctrine_ConnectionMock.php @@ -8,6 +8,7 @@ class Doctrine_ConnectionMock extends Doctrine_Connection protected $_driverName = 'Mock'; private $_sequenceModuleMock; private $_platformMock; + private $_inserts = array(); public function __construct(array $params) { @@ -30,6 +31,14 @@ class Doctrine_ConnectionMock extends Doctrine_Connection return $this->_platformMock; } + /** + * @override + */ + public function insert($tableName, array $data) + { + $this->_inserts[$tableName][] = $data; + } + /* Mock API */ public function setDatabasePlatform($platform) @@ -41,6 +50,16 @@ class Doctrine_ConnectionMock extends Doctrine_Connection { $this->_sequenceModuleMock = $seqManager; } + + public function getInserts() + { + return $this->_inserts; + } + + public function reset() + { + $this->_inserts = array(); + } } ?> \ No newline at end of file diff --git a/tests/models/cms/CmsArticle.php b/tests/models/cms/CmsArticle.php index 90083948e..e04a9b442 100755 --- a/tests/models/cms/CmsArticle.php +++ b/tests/models/cms/CmsArticle.php @@ -34,7 +34,18 @@ class CmsArticle extends Doctrine_Entity 'type' => 'integer', 'length' => 4 )); - $mapping->hasMany('CmsComment as comments', array( - 'local' => 'id', 'foreign' => 'article_id')); + + /*$mapping->hasMany('CmsComment as comments', array( + 'local' => 'id', 'foreign' => 'article_id'));*/ + + $mapping->mapOneToMany(array( + 'fieldName' => 'comments', + 'targetEntity' => 'CmsComment', + )); + + /*$mapping->mapManyToOne(array( + 'fieldName' => 'author', + 'joinColumns' => array('user_id' => 'id') + ));*/ } } diff --git a/tests/models/cms/CmsUser.php b/tests/models/cms/CmsUser.php index 961cf2956..74f51938b 100644 --- a/tests/models/cms/CmsUser.php +++ b/tests/models/cms/CmsUser.php @@ -36,9 +36,21 @@ class CmsUser extends Doctrine_Entity 'length' => 255 )); - $mapping->hasMany('CmsPhonenumber as phonenumbers', array( + /*$mapping->hasMany('CmsPhonenumber as phonenumbers', array( 'local' => 'id', 'foreign' => 'user_id')); $mapping->hasMany('CmsArticle as articles', array( - 'local' => 'id', 'foreign' => 'user_id')); + 'local' => 'id', 'foreign' => 'user_id'));*/ + + $mapping->mapOneToMany(array( + 'fieldName' => 'phonenumbers', + 'targetEntity' => 'CmsPhonenumber', + + )); + + $mapping->mapOneToMany(array( + 'fieldName' => 'articles', + 'targetEntity' => 'CmsArticle', + )); + } } diff --git a/tests/models/forum/ForumAvatar.php b/tests/models/forum/ForumAvatar.php new file mode 100644 index 000000000..7fa426711 --- /dev/null +++ b/tests/models/forum/ForumAvatar.php @@ -0,0 +1,20 @@ +mapField(array( + 'fieldName' => 'id', + 'type' => 'integer', + 'length' => 4, + 'id' => true, + 'idGenerator' => 'auto' + )); + } + +} + + +?> \ No newline at end of file diff --git a/tests/models/forum/ForumBoard.php b/tests/models/forum/ForumBoard.php index 1b176cec1..ca2c66c1d 100755 --- a/tests/models/forum/ForumBoard.php +++ b/tests/models/forum/ForumBoard.php @@ -25,14 +25,13 @@ class ForumBoard extends Doctrine_Entity 'type' => 'integer' )); - $mapping->hasOne('ForumCategory as category', - array('local' => 'category_id', 'foreign' => 'id')); - /* - $metadata->mapOneToOne(array( - 'fieldName' => 'category', // optional, defaults to targetEntity + /*$mapping->hasOne('ForumCategory as category', + array('local' => 'category_id', 'foreign' => 'id'));*/ + + $mapping->mapOneToOne(array( + 'fieldName' => 'category', 'targetEntity' => 'ForumCategory', 'joinColumns' => array('category_id' => 'id') - )); - */ + )); } } diff --git a/tests/models/forum/ForumCategory.php b/tests/models/forum/ForumCategory.php index 0021ba6ad..7ed242bb6 100755 --- a/tests/models/forum/ForumCategory.php +++ b/tests/models/forum/ForumCategory.php @@ -20,7 +20,12 @@ class ForumCategory extends Doctrine_Entity 'length' => 255 )); - $mapping->hasMany('ForumBoard as boards', array( - 'local' => 'id' , 'foreign' => 'category_id')); + /*$mapping->hasMany('ForumBoard as boards', array( + 'local' => 'id' , 'foreign' => 'category_id'));*/ + + $mapping->mapOneToMany(array( + 'fieldName' => 'boards', + 'targetEntity' => 'ForumBoard' + )); } } diff --git a/tests/models/forum/ForumUser.php b/tests/models/forum/ForumUser.php index 917f00952..668397364 100644 --- a/tests/models/forum/ForumUser.php +++ b/tests/models/forum/ForumUser.php @@ -6,9 +6,9 @@ class ForumUser extends Doctrine_Entity { - #protected $dtype; #protected $id; #protected $username; + #protected $avatar; public static function initMetadata($mapping) { @@ -42,6 +42,12 @@ class ForumUser extends Doctrine_Entity 'length' => 50 )); + $mapping->mapOneToOne(array( + 'fieldName' => 'avatar', + 'targetEntity' => 'ForumAvatar', + 'joinColumns' => array('avatar_id' => 'id'), + )); + } } \ No newline at end of file