diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php new file mode 100644 index 000000000..dff47e352 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -0,0 +1,460 @@ +. + */ + +#namespace Doctrine::ORM::Mapping; + +/** + * Base class for association mappings. + * + * @author Roman Borschel + * @since 2.0 + * @todo Rename to AssociationMapping. + */ +class Doctrine_ORM_Mapping_AssociationMapping implements Serializable +{ + const FETCH_MANUAL = 1; + const FETCH_LAZY = 2; + const FETCH_EAGER = 3; + + /** + * Cascade types enumeration. + * + * @var array + */ + protected static $_cascadeTypes = array( + 'all', + 'none', + 'save', + 'delete', + 'refresh' + ); + + protected $_cascades = array(); + protected $_isCascadeDelete; + protected $_isCascadeSave; + protected $_isCascadeRefresh; + + protected $_customAccessor; + protected $_customMutator; + + /** + * The fetch mode used for the association. + * + * @var integer + */ + protected $_fetchMode = self::FETCH_MANUAL; + + /** + * Flag that indicates whether the class that defines this mapping is + * the owning side of the association. + * + * @var boolean + */ + protected $_isOwningSide = true; + + /** + * 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). + * + * @var string + */ + protected $_sourceEntityName; + + /** + * The name of the target Entity (the Enitity that is the target of the + * association). + * + * @var string + */ + protected $_targetEntityName; + + /** + * Identifies the field on the source class (the class this AssociationMapping + * belongs to) that represents the association and stores the reference to the + * other entity/entities. + * + * @var string + */ + protected $_sourceFieldName; + + /** + * Identifies the field on the owning side that controls the mapping for the + * association. This is only set on the inverse side of an association. + * + * @var string + */ + protected $_mappedByFieldName; + + /** + * Identifies the field on the inverse side of a bidirectional association. + * This is only set on the owning side of an association. + * + * @var string + */ + //protected $_inverseSideFieldName; + + /** + * The name of the join table, if any. + * + * @var string + */ + protected $_joinTable; + + //protected $_mapping = array(); + + /** + * Constructor. + * Creates a new AssociationMapping. + * + * @param array $mapping The mapping definition. + */ + public function __construct(array $mapping) + { + //$this->_initMappingArray(); + //$mapping = $this->_validateAndCompleteMapping($mapping); + //$this->_mapping = array_merge($this->_mapping, $mapping);*/ + + $this->_validateAndCompleteMapping($mapping); + } + + protected function _initMappingArray() + { + $this->_mapping = array( + 'fieldName' => null, + 'sourceEntity' => null, + 'targetEntity' => null, + 'mappedBy' => null, + 'joinColumns' => null, + 'joinTable' => null, + 'accessor' => null, + 'mutator' => null, + 'optional' => true, + 'cascades' => array() + ); + } + + /** + * Validates & completes the mapping. Mapping defaults are applied here. + * + * @param array $mapping + */ + protected function _validateAndCompleteMapping(array $mapping) + { + // Mandatory attributes for both sides + if ( ! isset($mapping['fieldName'])) { + throw Doctrine_MappingException::missingFieldName(); + } + $this->_sourceFieldName = $mapping['fieldName']; + + if ( ! isset($mapping['sourceEntity'])) { + throw Doctrine_MappingException::missingSourceEntity($mapping['fieldName']); + } + $this->_sourceEntityName = $mapping['sourceEntity']; + + if ( ! isset($mapping['targetEntity'])) { + throw Doctrine_MappingException::missingTargetEntity($mapping['fieldName']); + } + $this->_targetEntityName = $mapping['targetEntity']; + + // Mandatory and optional attributes for either side + if ( ! isset($mapping['mappedBy'])) { + // Optional + if (isset($mapping['joinTable'])) { + $this->_joinTable = $mapping['joinTable']; + } + } else { + $this->_isOwningSide = false; + $this->_mappedByFieldName = $mapping['mappedBy']; + } + + // Optional attributes for both sides + if (isset($mapping['accessor'])) { + $this->_customAccessor = $mapping['accessor']; + } + if (isset($mapping['mutator'])) { + $this->_customMutator = $mapping['mutator']; + } + $this->_isOptional = isset($mapping['optional']) ? + (bool)$mapping['optional'] : true; + $this->_cascades = isset($mapping['cascade']) ? + (array)$mapping['cascade'] : array(); + } + + /** + * 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)) { + $this->_isCascadeDelete = in_array('delete', $this->_cascades); + } + 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)) { + $this->_isCascadeSave = in_array('save', $this->_cascades); + } + 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)) { + $this->_isCascadeRefresh = in_array('refresh', $this->_cascades); + } + 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; + } + + /** + * Gets the name of the join table. + * + * @return string + */ + public function getJoinTable() + { + return $this->_joinTable; + } + + /** + * 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; + } + + /*public function getInverseSideFieldName() + { + return $this->_inverseSideFieldName; + }*/ + /** + * Marks the association as bidirectional, specifying the field name of + * the inverse side. + * This is called on the owning side, when an inverse side is discovered. + * This does only make sense to call on the owning side. + * + * @param string $inverseSideFieldName + */ + /*public function setBidirectional($inverseSideFieldName) + { + if ( ! $this->_isOwningSide) { + return; //TODO: exception? + } + $this->_inverseSideFieldName = $inverseSideFieldName; + }*/ + + /** + * Whether the association is bidirectional. + * + * @return boolean + */ + /*public function isBidirectional() + { + return $this->_mappedByFieldName || $this->_inverseSideFieldName; + }*/ + + /** + * 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() + { + return ""; + } + + public function unserialize($serialized) + { + return true; + } +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php new file mode 100644 index 000000000..8bafd546c --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -0,0 +1,88 @@ +isOwningSide()) { + // many-many owning MUST have a join table + if ( ! isset($mapping['joinTable'])) { + throw Doctrine_MappingException::joinTableRequired($mapping['fieldName']); + } + + // optional attributes for many-many owning side + $this->_associationClass = isset($mapping['associationClass']) ? + $mapping['associationClass'] : null; + } + } + + /** + * Whether the mapping uses an association class for the intermediary + * table. + * + * @return boolean + */ + public function usesAssociationClass() + { + return $this->_associationClass !== null; + } + + /** + * Gets the name of the association class. + * + * @return string + */ + public function getAssociationClassName() + { + return $this->_associationClass; + } +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php new file mode 100644 index 000000000..c1785deba --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -0,0 +1,112 @@ +. + */ + +#namespace Doctrine::ORM::Mapping; + +/** + * Represents a one-to-many mapping. + * + * NOTE: One-to-many mappings can currently not be uni-directional (one -> many). + * They must either be bidirectional (one <-> many) or unidirectional (many -> one). + * In other words, the many-side MUST be the owning side and the one-side MUST be + * the inverse side. + * + * @author Roman Borschel + * @since 2.0 + * @todo Rename to OneToManyMapping + */ +class Doctrine_ORM_Mapping_OneToManyMapping extends Doctrine_ORM_Mapping_AssociationMapping +{ + /** The target foreign key columns that reference the sourceKeyColumns. */ + /* NOTE: Currently not used because uni-directional one-many not supported atm. */ + //protected $_targetForeignKeyColumns; + + /** The (typically primary) source key columns that are referenced by the targetForeignKeyColumns. */ + /* NOTE: Currently not used because uni-directional one-many not supported atm. */ + //protected $_sourceKeyColumns; + + /** This maps the target foreign key columns to the corresponding (primary) source key columns. */ + /* NOTE: Currently not used because uni-directional one-many not supported atm. */ + //protected $_targetForeignKeysToSourceKeys; + + /** This maps the (primary) source key columns to the corresponding target foreign key columns. */ + /* NOTE: Currently not used because uni-directional one-many not supported atm. */ + //protected $_sourceKeysToTargetForeignKeys; + + /** Whether to delete orphaned elements (removed from the collection) */ + protected $_deleteOrphans = false; + + /** + * Constructor. + * Creates a new OneToManyMapping. + * + * @param array $mapping The mapping info. + */ + public function __construct(array $mapping) + { + parent::__construct($mapping); + } + + /** + * Validates and completed the mapping. + * + * @param array $mapping The mapping to validate and complete. + * @return array The validated and completed mapping. + * @override + */ + protected function _validateAndCompleteMapping(array $mapping) + { + parent::_validateAndCompleteMapping($mapping); + + // one-side MUST be inverse (must have mappedBy) + if ( ! isset($mapping['mappedBy'])) { + throw Doctrine_MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); + } + + $this->_deleteOrphans = isset($mapping['deleteOrphans']) ? + (bool)$mapping['deleteOrphans'] : false; + } + + /** + * Whether orphaned elements (removed from the collection) should be deleted. + * + * @return boolean TRUE if orphaned elements should be deleted, FALSE otherwise. + */ + public function shouldDeleteOrphans() + { + return $this->_deleteOrphans; + } + + /** + * Whether the association is one-to-many. + * + * @return boolean TRUE if the association is one-to-many, FALSE otherwise. + * @override + */ + public function isOneToMany() + { + return true; + } + + +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php new file mode 100644 index 000000000..05c47f42a --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -0,0 +1,164 @@ +. + */ + +#namespace Doctrine::ORM::Mappings; + +#use Doctrine::ORM::Entity; + +/** + * A one-to-one mapping describes a uni-directional mapping from one entity + * to another entity. + * + * @since 2.0 + * @author Roman Borschel + * @todo Rename to OneToOneMapping + */ +class Doctrine_ORM_Mapping_OneToOneMapping extends Doctrine_ORM_Mapping_AssociationMapping +{ + /** + * Maps the source foreign/primary key columns to the target primary/foreign key columns. + * i.e. source.id (pk) => target.user_id (fk). + * Reverse mapping of _targetToSourceKeyColumns. + */ + protected $_sourceToTargetKeyColumns = array(); + + /** + * Maps the target primary/foreign key columns to the source foreign/primary key columns. + * i.e. target.user_id (fk) => source.id (pk). + * Reverse mapping of _sourceToTargetKeyColumns. + */ + protected $_targetToSourceKeyColumns = array(); + + /** + * Whether to delete orphaned elements (when nulled out, i.e. $foo->other = null) + * + * @var boolean + */ + protected $_deleteOrphans = false; + + /** + * Constructor. + * Creates a new OneToOneMapping. + * + * @param array $mapping The mapping info. + */ + public function __construct(array $mapping) + { + parent::__construct($mapping); + } + + protected function _initMappingArray() + { + parent::_initMappingArray(); + $this->_mapping['deleteOrphans'] = false; + } + + /** + * Validates & completes the mapping. Mapping defaults are applied here. + * + * @param array $mapping The mapping to validate & complete. + * @return array The validated & completed mapping. + * @override + */ + protected function _validateAndCompleteMapping(array $mapping) + { + parent::_validateAndCompleteMapping($mapping); + + if ($this->isOwningSide()) { + if ( ! isset($mapping['joinColumns'])) { + throw Doctrine_MappingException::invalidMapping($this->_sourceFieldName); + } + $this->_sourceToTargetKeyColumns = $mapping['joinColumns']; + $this->_targetToSourceKeyColumns = array_flip($this->_sourceToTargetKeyColumns); + } + + $this->_deleteOrphans = isset($mapping['deleteOrphans']) ? + (bool)$mapping['deleteOrphans'] : false; + + return $mapping; + } + + /** + * Gets the source-to-target key column mapping. + * + * @return unknown + */ + public function getSourceToTargetKeyColumns() + { + return $this->_sourceToTargetKeyColumns; + } + + /** + * Gets the target-to-source key column mapping. + * + * @return unknown + */ + public function getTargetToSourceKeyColumns() + { + return $this->_targetToSourceKeyColumns; + } + + /** + * Whether the association is one-to-one. + * + * @return boolean + * @override + */ + public function isOneToOne() + { + return true; + } + + /** + * Lazy-loads the associated entity for a given entity. + * + * @param Doctrine::ORM::Entity $entity + * @return void + */ + public function lazyLoadFor(Doctrine_Entity $entity) + { + if ($entity->getClassName() != $this->_sourceClass->getClassName()) { + //error? + } + + $dql = 'SELECT t.* FROM ' . $this->_targetClass->getClassName() . ' t WHERE '; + $params = array(); + foreach ($this->_sourceToTargetKeyFields as $sourceKeyField => $targetKeyField) { + if ($params) { + $dql .= " AND "; + } + $dql .= "t.$targetKeyField = ?"; + $params[] = $entity->_rawGetField($sourceKeyField); + } + + $otherEntity = $this->_targetClass->getEntityManager() + ->query($dql, $params) + ->getFirst(); + + if ( ! $otherEntity) { + $otherEntity = Doctrine_Null::$INSTANCE; + } + $entity->_internalSetReference($this->_sourceFieldName, $otherEntity); + } + +} + +?> \ No newline at end of file