. */ /** * A ClassMetadata instance holds all the information (metadata) of an entity and * it's associations and how they're mapped to the relational model. * * @package Doctrine * @subpackage ClassMetadata * @author Roman Borschel * @since 1.0 */ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializable { /** * The name of the entity class that is mapped to the database with this metadata. * * @var string */ protected $_entityName; /** * The name of the entity class that is at the root of the entity inheritance * hierarchy. If the entity is not part of an inheritance hierarchy this is the same * as the $_entityName. * * @var string */ protected $_rootEntityName; /** * The name of the custom mapper class used for the entity class. * (Optional). * * @var string */ protected $_customMapperClassName; /** * * @var Doctrine_Connection */ protected $_conn; /** * The names of the parent classes (ancestors). */ protected $_parentClasses = array(); /** * The field names of all fields that are part of the identifier/primary key * of the described entity class. * * @var array */ protected $_identifier = array(); /** * The identifier type of the class. * * @see Doctrine::IDENTIFIER_* constants * @var integer */ protected $_identifierType; /** * The inheritance mapping type used by the class. * * * @var integer */ protected $_inheritanceType = Doctrine::INHERITANCETYPE_TABLE_PER_CLASS; /** * An array containing all behaviors attached to the class. * * @see Doctrine_Template * @var array $_templates * @todo Unify under 'Behaviors'. */ protected $_behaviors = array(); /** * An array containing all behavior generators attached to the class. * * @see Doctrine_Record_Generator * @var array $_generators * @todo Unify under 'Behaviors'. */ protected $_generators = array(); /** * An array containing all filters attached to the class. * * @see Doctrine_Record_Filter * @var array $_filters */ protected $_filters = array(); /** * The mapped columns and their mapping definitions. * Keys are column names and values are mapping definitions. * * The mapping definition array has at least the following values: * * -- type the column type, eg. 'integer' * -- length the column length, eg. 11 * * additional keys: * -- notnull whether or not the column is marked as notnull * -- values enum values * -- notblank notblank validator + notnull constraint * ... many more * * @var array $columns */ protected $_mappedColumns = array(); /** * An array of field names. used to look up field names from column names. * Keys are column names and values are field names. * This is the reverse lookup map of $_columnNames. * * @var array */ protected $_fieldNames = array(); /** * An array of column names. Keys are field names and values column names. * Used to look up column names from field names. * This is the reverse lookup map of $_fieldNames. * * @var array */ protected $_columnNames = array(); /** * Caches enum value mappings. Keys are field names and values arrays with the * mapping. */ protected $_enumValues = array(); /** * Tree object associated with the class. * * @var Doctrine_Tree * @todo Belongs to the NestedSet Behavior plugin. */ protected $_tree; /** * Cached column count, Doctrine_Record uses this column count when * determining its state. * * @var integer */ protected $_columnCount; /** * Whether or not this class has default values. * * @var boolean */ protected $_hasDefaultValues; /** * Relation parser object. Manages the relations for the class. * * @var Doctrine_Relation_Parser $_parser */ protected $_parser; /** * Enum value arrays. */ protected $_enumMap = array(); /** * @var array $options an array containing all options * * -- parents the parent classes of this component * * -- treeImpl the tree implementation of this table (if any) * * -- treeOptions the tree options * * -- queryParts the bound query parts */ protected $_options = array( 'treeImpl' => null, 'treeOptions' => null, 'queryParts' => array(), 'subclasses' => array(), 'parents' => array() ); /** * Inheritance options. */ protected $_inheritanceOptions = array( // JOINED & TABLE_PER_CLASS options 'discriminatorColumn' => null, 'discriminatorMap' => array(), // JOINED options 'joinSubclasses' => true ); /** * Specific options that can be set for the database table the class is mapped to. * Some of them are dbms specific and they are only used if the table is generated * by Doctrine (NOT when using Migrations). * * -- type table type (mysql example: INNODB) * * -- charset character set * * -- checks the check constraints of this table, eg. 'price > dicounted_price' * * -- collation collation attribute * * -- indexes the index definitions of this table * * -- sequenceName Some databases need sequences instead of auto incrementation primary keys, * you can set specific sequence for your table by calling setOption('sequenceName', $seqName) * where $seqName is the name of the desired sequence */ protected $_tableOptions = array( 'tableName' => null, 'sequenceName' => null, 'type' => null, 'charset' => null, 'collation' => null, 'collate' => null, 'indexes' => array(), 'checks' => array() ); /** * @var array $_invokedMethods method invoker cache */ protected $_invokedMethods = array(); /** * Constructs a new ClassMetadata instance. * * @param string $entityName Name of the entity class the metadata info is used for. */ public function __construct($entityName, Doctrine_Connection $conn) { $this->_entityName = $entityName; $this->_rootEntityName = $entityName; $this->_conn = $conn; $this->_parser = new Doctrine_Relation_Parser($this); $this->_filters[] = new Doctrine_Record_Filter_Standard(); $this->setConfigurableParent($this->_conn); } /** * */ public function getConnection() { return $this->_conn; } /** * getComponentName * * @return string */ public function getClassName() { return $this->_entityName; } /** * Gets the name of the root class of the entity hierarchy. If the entity described * by the ClassMetadata is not participating in a hierarchy, this is the same as the * name returned by {@link getClassName()}. * * @return string */ public function getRootClassName() { return $this->_rootEntityName; } /** * @deprecated */ public function getComponentName() { return $this->getClassName(); } /** * Checks whether a field is part of the identifier/primary key field(s). * * @param string $fieldName The field name * @return boolean TRUE if the field is part of the table identifier/primary key field(s), * FALSE otherwise. */ public function isIdentifier($fieldName) { return in_array($fieldName, $this->getIdentifier()); } /** * addIndex * * adds an index to this table * * @return void */ public function addIndex($index, array $definition) { $this->_tableOptions['indexes'][$index] = $definition; } /** * getIndex * * @return array|boolean array on success, FALSE on failure */ public function getIndex($index) { if (isset($this->_tableOptions['indexes'][$index])) { return $this->_tableOptions['indexes'][$index]; } 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) { /*switch ($name) { case 'tableName': case 'index': case 'sequenceName': case 'type': case 'charset': case 'collation': case 'collate': return $this->setTableOption($name, $value); case 'enumMap': $this->_enumMap = $value; return; }*/ $this->_options[$name] = $value; } /** * Sets a table option. */ public function setTableOption($name, $value) { if ( ! array_key_exists($name, $this->_tableOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown table option: '$name'."); } $this->_tableOptions[$name] = $value; } /** * Gets a table option. */ public function getTableOption($name) { if ( ! array_key_exists($name, $this->_tableOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown table option: '$name'."); } 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; } /** * getOption * 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. * * @return array all options and their values */ public function getTableOptions() { return $this->_tableOptions; } /** * getColumnName * * returns a column name for a field name. * if the column name for the field cannot be found * this method returns the given field name. * * @param string $alias column alias * @return string column name */ public function getColumnName($fieldName) { return isset($this->_columnNames[$fieldName]) ? $this->_columnNames[$fieldName] : $fieldName; } /** * @deprecated */ public function getColumnDefinition($columnName) { return $this->getColumnMapping($columnName); } public function getColumnMapping($columnName) { return isset($this->_mappedColumns[$columnName]) ? $this->_mappedColumns[$columnName] : false; } /** * getFieldName * * returns the field name for a column name * if no field name can be found the column name is returned. * * @param string $columnName column name * @return string column alias */ public function getFieldName($columnName) { return isset($this->_fieldNames[$columnName]) ? $this->_fieldNames[$columnName] : $columnName; } /** * @deprecated */ public function setColumns(array $definitions) { foreach ($definitions as $name => $options) { $this->setColumn($name, $options['type'], $options['length'], $options); } } /** * Maps a column of the class' database table to a field of the entity. * * @param string $name The name of the column to map. Syntax: columnName [as propertyName]. * The property name is optional. If not used the column will be * mapped to a property with the same name. * @param string $type The type of the column. * @param integer $length The length of the column. * @param mixed $options * @param boolean $prepend Whether to prepend or append the new column to the column list. * By default the column gets appended. * * @throws Doctrine_ClassMetadata_Exception If trying use wrongly typed parameter. */ public function mapColumn($name, $type, $length = null, $options = array(), $prepend = false) { foreach ($options as $k => $option) { if (is_numeric($k)) { if ( ! empty($option) && $option !== false) { $options[$option] = true; } unset($options[$k]); } } // extract column name & field name $parts = explode(' as ', $name); if (count($parts) > 1) { $fieldName = $parts[1]; } else { $fieldName = $parts[0]; } $name = strtolower($parts[0]); if (isset($this->_columnNames[$fieldName])) { return; } if ($prepend) { $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames); $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames); } else { $this->_columnNames[$fieldName] = $name; $this->_fieldNames[$name] = $fieldName; } if ($length == null) { switch ($type) { case 'string': case 'clob': case 'float': case 'integer': case 'array': case 'object': case 'blob': case 'gzip': // use php int max $length = 2147483647; break; case 'boolean': $length = 1; case 'date': // YYYY-MM-DD ISO 8601 $length = 10; case 'time': // HH:NN:SS+00:00 ISO 8601 $length = 14; case 'timestamp': // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601 $length = 25; break; } } $options['type'] = $type; $options['length'] = $length; if ($prepend) { $this->_mappedColumns = array_merge(array($name => $options), $this->_mappedColumns); } else { $this->_mappedColumns[$name] = $options; } if ( ! empty($options['primary'])) { if ( ! in_array($fieldName, $this->_identifier)) { $this->_identifier[] = $fieldName; } } if (isset($options['default'])) { $this->_hasDefaultValues = true; } $this->_columnCount++; } /** * setColumn * * @param string $name * @param string $type * @param integer $length * @param mixed $options * @param boolean $prepend Whether to prepend or append the new column to the column list. * By default the column gets appended. * @throws Doctrine_Table_Exception if trying use wrongly typed parameter * @return void * @deprecated Use mapColumn() */ public function setColumn($name, $type, $length = null, $options = array(), $prepend = false) { return $this->mapColumn($name, $type, $length, $options, $prepend); } /** * 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) { $columnName = $this->getColumnName($fieldName); return isset($this->_mappedColumns[$columnName]['validators']) ? $this->_mappedColumns[$columnName]['validators'] : array(); } /** * Checks whether the class mapped class has a default value on any field. * * @return boolean TRUE if the entity has a default value on any field, otherwise false. */ public function hasDefaultValues() { return $this->_hasDefaultValues; } /** * getDefaultValueOf * returns the default value(if any) for given field * * @param string $fieldName * @return mixed */ public function getDefaultValueOf($fieldName) { $columnName = $this->getColumnName($fieldName); if ( ! isset($this->_mappedColumns[$columnName])) { throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist."); } if (isset($this->_mappedColumns[$columnName]['default'])) { return $this->_mappedColumns[$columnName]['default']; } else { return null; } } /** * Gets the identifier (primary key) field(s) of the mapped class. * * @return mixed * @deprecated Use getIdentifierFieldNames() */ public function getIdentifier() { return $this->_identifier; } /** * Gets the identifier (primary key) field(s) of the mapped class. * * @return mixed */ public function getIdentifierFieldNames() { return $this->_identifier; } public function setIdentifier(array $identifier) { $this->_identifier = $identifier; } /** * Gets the type of the identifier (primary key) used by the mapped class. The type * can be either Doctrine::IDENTIFIER_NATURAL, Doctrine::IDENTIFIER_AUTOINCREMENT, * Doctrine::IDENTIFIER_SEQUENCE or Doctrine::IDENTIFIER_COMPOSITE. * * @return integer */ public function getIdentifierType() { return $this->_identifierType; } /** * Sets the identifier type used by the mapped class. */ public function setIdentifierType($type) { $this->_identifierType = $type; } /** * hasColumn * @return boolean * @deprecated */ public function hasColumn($columnName) { return isset($this->_mappedColumns[$columnName]); } public function hasMappedColumn($columnName) { return isset($this->_mappedColumns[$columnName]); } /** * hasField * @return boolean */ public function hasField($fieldName) { return isset($this->_columnNames[$fieldName]); } /** * @param string $fieldName * @return array */ public function getEnumValues($fieldName) { $columnName = $this->getColumnName($fieldName); if (isset($this->_mappedColumns[$columnName]['values'])) { return $this->_mappedColumns[$columnName]['values']; } else { return array(); } } /** * enumValue * * @param string $field * @param integer $index * @return mixed */ public function enumValue($fieldName, $index) { if ($index instanceof Doctrine_Null) { return $index; } if (isset($this->_enumValues[$fieldName][$index])) { return $this->_enumValues[$fieldName][$index]; } $columnName = $this->getColumnName($fieldName); if ( ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM) && isset($this->_mappedColumns[$columnName]['values'][$index])) { $enumValue = $this->_mappedColumns[$columnName]['values'][$index]; } else { $enumValue = $index; } $this->_enumValues[$fieldName][$index] = $enumValue; return $enumValue; } /** * enumIndex * * @param string $field * @param mixed $value * @return mixed */ public function enumIndex($fieldName, $value) { $values = $this->getEnumValues($fieldName); $index = array_search($value, $values); if ($index === false || ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) { return $index; } return $value; } /** * getColumnCount * * @return integer the number of columns in this table * @deprecated */ public function getColumnCount() { return $this->_columnCount; } /** * getMappedColumnCount * * @return integer the number of mapped columns in the class. */ public function getMappedColumnCount() { return $this->_columnCount; } /** * * @return string The name of the accessor (getter) method or NULL if the field does * not have a custom accessor. */ public function getCustomAccessor($fieldName) { $columnName = $this->getColumnName($fieldName); return isset($this->_mappedColumns[$columnName]['accessor']) ? $this->_mappedColumns[$columnName]['accessor'] : null; } /** * * @return string The name of the mutator (setter) method or NULL if the field does * not have a custom mutator. */ public function getCustomMutator($fieldName) { $columnName = $this->getColumnName($fieldName); return isset($this->_mappedColumns[$columnName]['mutator']) ? $this->_mappedColumns[$columnName]['mutator'] : null; } /** * returns all columns and their definitions * * @return array * @deprecated */ public function getColumns() { return $this->_mappedColumns; } /** * Gets all mapped columns and their mapping definitions. * * @return array */ public function getMappedColumns() { return $this->_mappedColumns; } /** * removeColumn * removes given column * * @return boolean */ public function removeColumn($fieldName) { $columnName = array_search($fieldName, $this->_fieldNames); unset($this->_fieldNames[$columnName]); if (isset($this->_mappedColumns[$columnName])) { unset($this->_mappedColumns[$columnName]); return true; } $this->_columnCount--; return false; } /** * returns an array containing all the column names. * * @return array */ public function getColumnNames(array $fieldNames = null) { if ($fieldNames === null) { return array_keys($this->_mappedColumns); } else { $columnNames = array(); foreach ($fieldNames as $fieldName) { $columnNames[] = $this->getColumnName($fieldName); } return $columnNames; } } /** * returns an array with all the identifier column names. * * @return array */ public function getIdentifierColumnNames() { return $this->getColumnNames((array) $this->getIdentifier()); } /** * returns an array containing all the field names. * * @return array */ public function getFieldNames() { return array_values($this->_fieldNames); } /** * getDefinitionOf * * @return mixed array on success, false on failure * @deprecated */ public function getDefinitionOf($fieldName) { $columnName = $this->getColumnName($fieldName); return $this->getColumnDefinition($columnName); } public function getMappingForField($fieldName) { $columnName = $this->getColumnName($fieldName); return $this->getColumnDefinition($columnName); } /** * getTypeOf * * @return mixed string on success, false on failure * @deprecated */ public function getTypeOf($fieldName) { return $this->getTypeOfColumn($this->getColumnName($fieldName)); } public function getTypeOfField($fieldName) { return $this->getTypeOfColumn($this->getColumnName($fieldName)); } /** * getTypeOfColumn * * @return mixed The column type or FALSE if the type cant be determined. */ public function getTypeOfColumn($columnName) { return isset($this->_mappedColumns[$columnName]) ? $this->_mappedColumns[$columnName]['type'] : false; } /** * Gets the (maximum) length of a field. */ public function getFieldLength($fieldName) { return $this->_mappedColumns[$this->getColumnName($fieldName)]['length']; } /** * getTableName * * @return void */ public function getTableName() { return $this->getTableOption('tableName'); } public function getInheritedFields() { } /** * Adds a named query. * * @param string $name The name under which the query gets registered. * @param string $query The DQL query. * @todo Implementation. */ public function addNamedQuery($name, $query) { } public function bindRelation($args, $type) { return $this->bind($args, $type); } /** * DESCRIBE WHAT THIS METHOD DOES, PLEASE! * * @todo Name proposal: addRelation */ public function bind($args, $type) { $options = array(); $options['type'] = $type; if ( ! isset($args[1])) { $args[1] = array(); } if ( ! is_array($args[1])) { try { throw new Exception(); } catch (Exception $e) { echo $e->getTraceAsString(); } } $options = array_merge($args[1], $options); $this->_parser->bind($args[0], $options); } /** * hasRelation * * @param string $alias the relation to check if exists * @return boolean true if the relation exists otherwise false */ public function hasRelation($alias) { return $this->_parser->hasRelation($alias); } /** * getRelation * * @param string $alias relation alias */ public function getRelation($alias, $recursive = true) { return $this->_parser->getRelation($alias, $recursive); } public function getRelationParser() { return $this->_parser; } /** * getRelations * returns an array containing all relation objects * * @return array an array of Doctrine_Relation objects */ public function getRelations() { return $this->_parser->getRelations(); } /** * 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. * * @return integer */ public function getInheritanceType() { return $this->_inheritanceType; } /** * Sets the subclasses of the class. * All entity classes that participate in a hierarchy and have subclasses * need to declare them this way. * * @param array $subclasses The names of all subclasses. */ public function setSubclasses(array $subclasses) { $this->_options['subclasses'] = $subclasses; } /** * Gets the names of all subclasses. * * @return array The names of all subclasses. */ public function getSubclasses() { return $this->getOption('subclasses'); } /** * Checks whether the class has any persistent subclasses. * * @return boolean TRUE if the class has one or more persistent subclasses, FALSE otherwise. */ public function hasSubclasses() { return ! $this->getOption('subclasses'); } /** * Gets the names of all parent classes. * * @return array The names of all parent classes. */ public function getParentClasses() { return $this->getOption('parents'); } /** * Sets the parent class names. */ public function setParentClasses(array $classNames) { $this->_options['parents'] = $classNames; $this->_rootEntityName = array_pop($classNames); } /** * Checks whether the class has any persistence parent classes. * * @return boolean TRUE if the class has one or more persistent parent classes, FALSE otherwise. */ public function hasParentClasses() { return ! $this->getOption('parents'); } /** * Sets the inheritance type used by the class and it's subclasses. * * @param integer $type */ public function setInheritanceType($type, array $options = array()) { if ($type == Doctrine::INHERITANCETYPE_SINGLE_TABLE) { $this->_checkRequiredDiscriminatorOptions($options); } else if ($type == Doctrine::INHERITANCETYPE_JOINED) { $this->_checkRequiredDiscriminatorOptions($options); } else if ($type == Doctrine::INHERITANCETYPE_TABLE_PER_CLASS) { // concrete table inheritance ... } else { throw new Doctrine_ClassMetadata_Exception("Invalid inheritance type '$type'."); } $this->_inheritanceType = $type; foreach ($options as $name => $value) { $this->setInheritanceOption($name, $value); } } /** * Checks if the 2 options 'discriminatorColumn' and 'discriminatorMap' are present. * If either of them is missing an exception is thrown. * * @param array $options The options. * @throws Doctrine_ClassMetadata_Exception If at least one of the required discriminator * options is missing. */ private function _checkRequiredDiscriminatorOptions(array $options) { if ( ! isset($options['discriminatorColumn'])) { throw new Doctrine_ClassMetadata_Exception("Missing option 'discriminatorColumn'." . " Inheritance types JOINED and SINGLE_TABLE require this option."); } else if ( ! isset($options['discriminatorMap'])) { throw new Doctrine_ClassMetadata_Exception("Missing option 'discriminatorMap'." . " Inheritance types JOINED and SINGLE_TABLE require this option."); } } /** * Gets an inheritance option. * */ public function getInheritanceOption($name) { if ( ! array_key_exists($name, $this->_inheritanceOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'."); } return $this->_inheritanceOptions[$name]; } /** * Gets all inheritance options. */ public function getInheritanceOptions() { return $this->_inheritanceOptions; } /** * Sets an inheritance option. */ public function setInheritanceOption($name, $value) { if ( ! array_key_exists($name, $this->_inheritanceOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'."); } switch ($name) { case 'discriminatorColumn': if ($value !== null && ! is_string($value)) { throw new Doctrine_ClassMetadata_Exception("Invalid value '$value' for option" . " 'discriminatorColumn'."); } break; case 'discriminatorMap': if ( ! is_array($value)) { throw new Doctrine_ClassMetadata_Exception("Value for option 'discriminatorMap'" . " must be an array."); } break; } $this->_inheritanceOptions[$name] = $value; } /** * 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. */ public function export() { $this->_conn->export->exportTable($this); } /** * getExportableFormat * Returns an array with all the information needed to create the main database table * for the class. * * @return array */ public function getExportableFormat($parseForeignKeys = true) { $columns = array(); $primary = array(); $allColumns = $this->getColumns(); // If the class is part of a Single Table Inheritance hierarchy, collect the fields // of all classes in the hierarchy. if ($this->_inheritanceType == Doctrine::INHERITANCETYPE_SINGLE_TABLE) { $parents = $this->getOption('parents'); if ($parents) { $rootClass = $this->_conn->getClassMetadata(array_pop($parents)); } else { $rootClass = $this; } $subClasses = $rootClass->getOption('subclasses'); foreach ($subClasses as $subClass) { $subClassMetadata = $this->_conn->getClassMetadata($subClass); $allColumns = array_merge($allColumns, $subClassMetadata->getColumns()); } } else if ($this->_inheritanceType == Doctrine::INHERITANCETYPE_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) { if ($this->getParentClasses() && isset($definition['autoincrement'])) { unset($allColumns[$name]['autoincrement']); } continue; } if (isset($definition['inherited']) && $definition['inherited'] === true) { unset($allColumns[$name]); } } } else if ($this->_inheritanceType == Doctrine::INHERITANCETYPE_TABLE_PER_CLASS) { // If this is a subclass, just remove existing autoincrement options on the pk if ($this->getParentClasses()) { foreach ($allColumns as $name => $definition) { if (isset($definition['primary']) && $definition['primary'] === true) { if (isset($definition['autoincrement'])) { unset($allColumns[$name]['autoincrement']); } } } } } // Convert enum & boolean default values foreach ($allColumns as $name => $definition) { switch ($definition['type']) { case 'enum': if (isset($definition['default'])) { $definition['default'] = $this->enumIndex($name, $definition['default']); } break; case 'boolean': if (isset($definition['default'])) { $definition['default'] = $this->_conn->convertBooleans($definition['default']); } break; } $columns[$name] = $definition; if (isset($definition['primary']) && $definition['primary']) { $primary[] = $name; } } // Collect foreign keys from the relations $options['foreignKeys'] = array(); if ($parseForeignKeys && $this->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_CONSTRAINTS) { $constraints = array(); $emptyIntegrity = array('onUpdate' => null, 'onDelete' => null); foreach ($this->getRelations() as $name => $relation) { $fk = $relation->toArray(); $fk['foreignTable'] = $relation->getTable()->getTableName(); if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) { if ($relation->hasConstraint()) { throw new Doctrine_Table_Exception("Badly constructed integrity constraints."); } continue; } $integrity = array('onUpdate' => $fk['onUpdate'], 'onDelete' => $fk['onDelete']); if ($relation instanceof Doctrine_Relation_LocalKey) { $def = array('local' => $relation->getLocal(), 'foreign' => $relation->getForeign(), 'foreignTable' => $relation->getTable()->getTableName()); if (($key = array_search($def, $options['foreignKeys'])) === false) { $options['foreignKeys'][] = $def; $constraints[] = $integrity; } else { if ($integrity !== $emptyIntegrity) { $constraints[$key] = $integrity; } } } } foreach ($constraints as $k => $def) { $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def); } } $options['primary'] = $primary; return array('tableName' => $this->getTableOption('tableName'), 'columns' => $columns, 'options' => array_merge($options, $this->getTableOptions())); } /** * getTemplate * * @param string $template * @return void * @todo Unify under 'Behaviors'. */ public function getBehavior($behaviorName) { if ( ! isset($this->_behaviors[$behaviorName])) { throw new Doctrine_Table_Exception('Template ' . $behaviorName . ' not loaded'); } return $this->_behaviors[$behaviorName]; } /** * @todo Unify under 'Behaviors'. */ public function hasBehavior($behaviorName) { return isset($this->_behaviors[$behaviorName]); } /** * @todo Unify under 'Behaviors'. */ public function addBehavior($behaviorName, Doctrine_Template $impl) { $this->_behaviors[$behaviorName] = $impl; return $this; } /** * @todo Unify under 'Behaviors'. */ public function getGenerators() { return $this->_generators; } /** * @todo Unify under 'Behaviors'. */ public function getGenerator($generator) { if ( ! isset($this->_generators[$generator])) { throw new Doctrine_Table_Exception('Generator ' . $generator . ' not loaded'); } return $this->_generators[$plugin]; } /** * @todo Unify under 'Behaviors'. */ public function hasGenerator($generator) { return isset($this->_generators[$generator]); } /** * @todo Unify under 'Behaviors'. */ public function addGenerator(Doctrine_Record_Generator $generator, $name = null) { if ($name === null) { $this->_generators[] = $generator; } else { $this->_generators[$name] = $generator; } return $this; } /** * loadBehavior * * @param string $template * @todo Unify under 'Behaviors'. */ public function loadBehavior($behavior, array $options = array()) { $this->actAs($behavior, $options); } /** * @todo Unify under 'Behaviors'. */ public function loadGenerator(Doctrine_Record_Generator $generator) { $generator->initialize($this->_table); $this->addGenerator($generator, get_class($generator)); } /** * unshiftFilter * * @param object Doctrine_Record_Filter $filter * @return object $this * @todo Remove filters, if possible. */ public function unshiftFilter(Doctrine_Record_Filter $filter) { $filter->setTable($this); array_unshift($this->_filters, $filter); return $this; } /** * getTree * * getter for associated tree * * @return mixed if tree return instance of Doctrine_Tree, otherwise returns false * @todo Belongs to the NestedSet Behavior. */ public function getTree() { if ($this->getOption('treeImpl')) { if ( ! $this->_tree) { $options = $this->getOption('treeOptions') ? $this->getOption('treeOptions') : array(); $this->_tree = Doctrine_Tree::factory($this, $this->getOption('treeImpl'), $options); } return $this->_tree; } return false; } /** * isTree * * determine if table acts as tree * * @return mixed if tree return true, otherwise returns false * @todo Belongs to the NestedSet Behavior. */ public function isTree() { return ( ! is_null($this->getOption('treeImpl'))) ? true : false; } /** * getFilters * * @return array $filters * @todo Remove filters, if possible. */ public function getFilters() { return $this->_filters; } /** * Checks whether a persistent field is inherited from a superclass. * * @return boolean TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedField($fieldName) { return isset($this->_mappedColumns[$this->getColumnName($fieldName)]['inherited']); } /** * bindQueryParts * binds query parts to given component * * @param array $queryParts an array of pre-bound query parts * @return Doctrine_Record this object */ public function bindQueryParts(array $queryParts) { $this->_options['queryParts'] = $queryParts; return $this; } /** * bindQueryPart * binds given value to given query part * * @param string $queryPart * @param mixed $value * @return Doctrine_Record this object */ public function bindQueryPart($queryPart, $value) { $this->_options['queryParts'][$queryPart] = $value; return $this; } /** * getBoundQueryPart * * @param string $queryPart * @return string $queryPart */ public function getBoundQueryPart($queryPart) { if ( ! isset($this->_options['queryParts'][$queryPart])) { return null; } return $this->_options['queryParts'][$queryPart]; } /** * Sets the name of the primary table the class is mapped to. * * @param string $tableName The table name. */ public function setTableName($tableName) { $this->setTableOption('tableName', $this->_conn->formatter->getTableName($tableName)); } /** * Serializes the metadata. * * Part of the implementation of the Serializable interface. * * @return string The serialized metadata. */ public function serialize() { return serialize($this->_mappedColumns); } /** * Reconstructs the metadata class from it's serialized representation. * * Part of the implementation of the Serializable interface. * * @param string $serialized The serialized metadata class. */ public function unserialize($serialized) { return true; } /** * @todo Implementation. */ public function oneToOne($targetEntity, $definition) { } /** * @todo Implementation. */ public function oneToMany($targetEntity, $definition) { } /** * @todo Implementation. */ public function manyToOne($targetEntity, $definition) { } /** * @todo Implementation. */ public function manyToMany($targetEntity, $definition) { } /** * 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_Record this object * @todo Should be done through $_tableOptions */ 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. */ public function setCustomMapperClass($mapperClassName) { if ( ! is_subclass_of($mapperClassName, 'Doctrine_Mapper')) { throw new Doctrine_ClassMetadata_Exception("The custom mapper must be a subclass" . " of Doctrine_Mapper."); } $this->_customMapperClassName = $mapperClassName; } /** * Gets the name of the custom mapper class used for the entity class. * * @return string|null The name of the custom mapper class or NULL if the entity * class does not have a custom mapper class. */ public function getCustomMapperClass() { return $this->_customMapperClassName; } /** * @todo Thoughts & Implementation. */ public function setType($type) { //Doctrine::CLASSTYPE_ENTITY //Doctrine::CLASSTYPE_MAPPED_SUPERCLASS //Doctrine::CLASSTYPE_TRANSIENT } /** * @todo Implementation. Immutable entities can not be updated or deleted once * they are created. This means the entity can only be modified as long as it's * in transient state (TCLEAN, TDIRTY). */ public function isImmutable() { return false; } public function isDiscriminatorColumn($columnName) { 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_Record 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_Record this object */ public function hasMany() { $this->bind(func_get_args(), Doctrine_Relation::MANY_AGGREGATE); return $this; } public function hasAttribute($key) { switch ($key) { case Doctrine::ATTR_SEQCOL_NAME: case Doctrine::ATTR_COLL_KEY: case Doctrine::ATTR_LOAD_REFERENCES: case Doctrine::ATTR_EXPORT: case Doctrine::ATTR_QUERY_LIMIT: case Doctrine::ATTR_VALIDATE: return true; default: return false; } } /** * */ public function __toString() { return spl_object_hash($this); } }