. */ /** * Doctrine_Table represents a database table * each Doctrine_Table holds the information of foreignKeys and associations * * * @author Konsta Vesterinen * @package Doctrine * @subpackage Table * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision: 6051 $ * @link www.phpdoctrine.org * @since 1.0 * @method mixed findBy*(mixed $value) magic finders; @see __call() * @method mixed findOneBy*(mixed $value) magic finders; @see __call() */ class Doctrine_Table extends Doctrine_Configurable implements Countable { /** * @var array $data temporary data which is then loaded into Doctrine_Record::$_data */ protected $_data = array(); /** * @var mixed $identifier The field names of all fields that are part of the identifier/primary key */ protected $_identifier = array(); /** * @see Doctrine_Identifier constants * @var integer $identifierType the type of identifier this table uses */ protected $_identifierType; /** * @var Doctrine_Connection $conn Doctrine_Connection object that created this table */ protected $_conn; /** * @var array $identityMap first level cache */ protected $_identityMap = array(); /** * @var Doctrine_Table_Repository $repository record repository */ protected $_repository; /** * @var array $columns an array of column definitions, * keys are column names and values are column definitions * * the definition array has atleast 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 */ protected $_columns = array(); /** * Array of unique sets of fields. These values are validated on save * * @var array $_uniques */ protected $_uniques = array(); /** * @var array $_fieldNames an array of field names, used to look up field names * from column names. Keys are column * names and values are field names. * Alias for columns are here. */ protected $_fieldNames = array(); /** * * @var array $_columnNames 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. */ protected $_columnNames = array(); /** * @var integer $columnCount cached column count, Doctrine_Record uses this column count in when * determining its state */ protected $columnCount; /** * @var boolean $hasDefaultValues whether or not this table has default values */ protected $hasDefaultValues; /** * @var array $options an array containing all options * * -- name name of the component, for example component name of the GroupTable is 'Group' * * -- parents the parent classes of this component * * -- declaringClass name of the table definition declaring class (when using inheritance the class * that defines the table structure can be any class in the inheritance hierarchy, * hence we need reflection to check out which class actually calls setTableDefinition) * * -- tableName database table name, in most cases this is the same as component name but in some cases * where one-table-multi-class inheritance is used this will be the name of the inherited 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 * * -- enumMap enum value arrays * * -- inheritanceMap inheritanceMap is used for inheritance mapping, keys representing columns and values * the column values that should correspond to child classes * * -- type table type (mysql example: INNODB) * * -- charset character set * * -- foreignKeys the foreign keys of this table * * -- checks the check constraints of this table, eg. 'price > dicounted_price' * * -- collate collate attribute * * -- indexes the index definitions of this table * * -- treeImpl the tree implementation of this table (if any) * * -- treeOptions the tree options * * -- queryParts the bound query parts * * -- versioning */ protected $_options = array('name' => null, 'tableName' => null, 'sequenceName' => null, 'inheritanceMap' => array(), 'enumMap' => array(), 'type' => null, 'charset' => null, 'collate' => null, 'treeImpl' => null, 'treeOptions' => array(), 'indexes' => array(), 'parents' => array(), 'joinedParents' => array(), 'queryParts' => array(), 'versioning' => null, 'subclasses' => array(), ); /** * @var Doctrine_Tree $tree tree object associated with this table */ protected $_tree; /** * @var Doctrine_Relation_Parser $_parser relation parser object */ protected $_parser; /** * @see Doctrine_Template * @var array $_templates an array containing all templates attached to this table */ protected $_templates = array(); /** * @see Doctrine_Record_Filter * @var array $_filters an array containing all record filters attached to this table */ protected $_filters = array(); /** * @see Doctrine_Record_Generator * @var array $_generators an array containing all generators attached to this table */ protected $_generators = array(); /** * @var array $_invokedMethods method invoker cache */ protected $_invokedMethods = array(); /** * @var Doctrine_Record $record empty instance of the given model */ protected $record; /** * the constructor * * @throws Doctrine_Connection_Exception if there are no opened connections * @param string $name the name of the component * @param Doctrine_Connection $conn the connection associated with this table * @param boolean $initDefinition whether to init the in-memory schema */ public function __construct($name, Doctrine_Connection $conn, $initDefinition = false) { $this->_conn = $conn; $this->_options['name'] = $name; $this->setParent($this->_conn); $this->_conn->addTable($this); $this->_parser = new Doctrine_Relation_Parser($this); if ($initDefinition) { $this->record = $this->initDefinition(); $this->initIdentifier(); $this->record->setUp(); // if tree, set up tree if ($this->isTree()) { $this->getTree()->setUp(); } } else { if ( ! isset($this->_options['tableName'])) { $this->setTableName(Doctrine_Inflector::tableize($this->_options['name'])); } } $this->_filters[] = new Doctrine_Record_Filter_Standard(); $this->_repository = new Doctrine_Table_Repository($this); if ($charset = $this->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_CHARSET)) { $this->_options['charset'] = $charset; } if ($collate = $this->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_COLLATE)) { $this->_options['collate'] = $collate; } $this->construct(); } /** * Construct template method. * * This method provides concrete Table classes with the possibility * to hook into the constructor procedure. It is called after the * Doctrine_Table construction process is finished. * * @return void */ public function construct() { } /** * Initializes the in-memory table definition. * * @param string $name */ public function initDefinition() { $name = $this->_options['name']; if ( ! class_exists($name) || empty($name)) { throw new Doctrine_Exception("Couldn't find class " . $name); } $record = new $name($this); $names = array(); $class = $name; // get parent classes do { if ($class === 'Doctrine_Record') { break; } $name = $class; $names[] = $name; } while ($class = get_parent_class($class)); if ($class === false) { throw new Doctrine_Table_Exception('Class "' . $name . '" must be a child class of Doctrine_Record'); } // reverse names $names = array_reverse($names); // save parents array_pop($names); $this->_options['parents'] = $names; // create database table if (method_exists($record, 'setTableDefinition')) { $record->setTableDefinition(); // get the declaring class of setTableDefinition method $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition'); $class = $method->getDeclaringClass(); } else { $class = new ReflectionClass($class); } $this->_options['joinedParents'] = array(); foreach (array_reverse($this->_options['parents']) as $parent) { if ($parent === $class->getName()) { continue; } $ref = new ReflectionClass($parent); if ($ref->isAbstract() || ! $class->isSubClassOf($parent)) { continue; } $parentTable = $this->_conn->getTable($parent); $found = false; $parentColumns = $parentTable->getColumns(); foreach ($parentColumns as $columnName => $definition) { if ( ! isset($definition['primary']) || $definition['primary'] === false) { if (isset($this->_columns[$columnName])) { $found = true; break; } else { if ( ! isset($parentColumns[$columnName]['owner'])) { $parentColumns[$columnName]['owner'] = $parentTable->getComponentName(); } $this->_options['joinedParents'][] = $parentColumns[$columnName]['owner']; } } else { unset($parentColumns[$columnName]); } } if ($found) { continue; } foreach ($parentColumns as $columnName => $definition) { $fullName = $columnName . ' as ' . $parentTable->getFieldName($columnName); $this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true); } break; } $this->_options['joinedParents'] = array_values(array_unique($this->_options['joinedParents'])); $this->_options['declaringClass'] = $class; // set the table definition for the given tree implementation if ($this->isTree()) { $this->getTree()->setTableDefinition(); } $this->columnCount = count($this->_columns); if ( ! isset($this->_options['tableName'])) { $this->setTableName(Doctrine_Inflector::tableize($class->getName())); } return $record; } /** * Initializes the primary key. * * Called in the construction process, builds the identifier definition * copying in the schema the list of the fields which constitutes * the primary key. * * @return void */ public function initIdentifier() { switch (count($this->_identifier)) { case 0: if ( ! empty($this->_options['joinedParents'])) { $root = current($this->_options['joinedParents']); $table = $this->_conn->getTable($root); $this->_identifier = $table->getIdentifier(); $this->_identifierType = ($table->getIdentifierType() !== Doctrine::IDENTIFIER_AUTOINC) ? $table->getIdentifierType() : Doctrine::IDENTIFIER_NATURAL; // add all inherited primary keys foreach ((array) $this->_identifier as $id) { $definition = $table->getDefinitionOf($id); // inherited primary keys shouldn't contain autoinc // and sequence definitions unset($definition['autoincrement']); unset($definition['sequence']); // add the inherited primary key column $fullName = $id . ' as ' . $table->getFieldName($id); $this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true); } } else { $identifierOptions = $this->getAttribute(Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS); $name = (isset($identifierOptions['name']) && $identifierOptions['name']) ? $identifierOptions['name']:'id'; $name = sprintf($name, $this->getTableName()); $definition = array('type' => (isset($identifierOptions['type']) && $identifierOptions['type']) ? $identifierOptions['type']:'integer', 'length' => (isset($identifierOptions['length']) && $identifierOptions['length']) ? $identifierOptions['length']:8, 'autoincrement' => isset($identifierOptions['autoincrement']) ? $identifierOptions['autoincrement']:true, 'primary' => isset($identifierOptions['primary']) ? $identifierOptions['primary']:true); unset($identifierOptions['name'], $identifierOptions['type'], $identifierOptions['length']); foreach ($identifierOptions as $key => $value) { if ( ! isset($definition[$key]) || ! $definition[$key]) { $definition[$key] = $value; } } $this->setColumn($name, $definition['type'], $definition['length'], $definition, true); $this->_identifier = $name; $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC; } $this->columnCount++; break; case 1: foreach ($this->_identifier as $pk) { $e = $this->getDefinitionOf($pk); $found = false; foreach ($e as $option => $value) { if ($found) { break; } $e2 = explode(':', $option); switch (strtolower($e2[0])) { case 'autoincrement': case 'autoinc': if ($value !== false) { $this->_identifierType = Doctrine::IDENTIFIER_AUTOINC; $found = true; } break; case 'seq': case 'sequence': $this->_identifierType = Doctrine::IDENTIFIER_SEQUENCE; $found = true; if (is_string($value)) { $this->_options['sequenceName'] = $value; } else { if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) { $this->_options['sequenceName'] = $sequence; } else { $this->_options['sequenceName'] = $this->_conn->formatter->getSequenceName($this->_options['tableName']); } } break; } } if ( ! isset($this->_identifierType)) { $this->_identifierType = Doctrine::IDENTIFIER_NATURAL; } } $this->_identifier = $pk; break; default: $this->_identifierType = Doctrine::IDENTIFIER_COMPOSITE; } } /** * Gets the owner of a column. * * The owner of a column is the name of the component in a hierarchy that * defines the column. * * @param string $columnName the column name * @return string the name of the owning/defining component */ public function getColumnOwner($columnName) { if (isset($this->_columns[$columnName]['owner'])) { return $this->_columns[$columnName]['owner']; } else { return $this->getComponentName(); } } /** * Gets the record instance for this table. * * The Doctrine_Table instance always holds at least one * instance of a model so that it can be reused for several things, * but primarily it is first used to instantiate all the internal * in memory schema definition. * * @return Doctrine_Record Empty instance of the record */ public function getRecordInstance() { if ( ! $this->record) { $this->record = new $this->_options['name']; } return $this->record; } /** * Checks whether a column is inherited from a component further up in the hierarchy. * * @param $columnName The column name * @return boolean TRUE if column is inherited, FALSE otherwise. */ public function isInheritedColumn($columnName) { return (isset($this->_columns[$columnName]['owner'])); } /** * Checks whether a field is in the primary key. * * Checks if $fieldName is part of the table identifier, which defines * the one-column or multi-column primary key. * * @param string $fieldName The field name * @return boolean TRUE if the field is part of the table identifier/primary key field(s), */ public function isIdentifier($fieldName) { return ($fieldName === $this->getIdentifier() || in_array($fieldName, (array) $this->getIdentifier())); } /** * Checks whether a field identifier is of type autoincrement. * * This method checks if the primary key is a AUTOINCREMENT column or * if the table uses a natural key. * * @return boolean TRUE if the identifier is autoincrement * FALSE otherwise */ public function isIdentifierAutoincrement() { return $this->getIdentifierType() === Doctrine::IDENTIFIER_AUTOINC; } /** * Checks whether a field identifier is a composite key. * * @return boolean TRUE if the identifier is a composite key, * FALSE otherwise */ public function isIdentifierComposite() { return $this->getIdentifierType() === Doctrine::IDENTIFIER_COMPOSITE; } /** * getMethodOwner * * @param string $method * @return void */ public function getMethodOwner($method) { return (isset($this->_invokedMethods[$method])) ? $this->_invokedMethods[$method] : false; } /** * setMethodOwner * * @param string $method * @param string $class */ public function setMethodOwner($method, $class) { $this->_invokedMethods[$method] = $class; } /** * Exports this table to database based on the schema definition. * * This method create a physical table in the database, using the * definition that comes from the component Doctrine_Record instance. * * @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); } /** * Returns an exportable representation of this object. * * This method produces a array representation of the table schema, where * keys are tableName, columns (@see $_columns) and options. * The options subarray contains 'primary' and 'foreignKeys'. * * @param boolean $parseForeignKeys whether to include foreign keys definition in the options * @return array */ public function getExportableFormat($parseForeignKeys = true) { $columns = array(); $primary = array(); foreach ($this->getColumns() as $name => $definition) { if (isset($definition['owner'])) { continue; } switch ($definition['type']) { case 'boolean': if (isset($definition['default'])) { $definition['default'] = $this->getConnection()->convertBooleans($definition['default']); } break; } $columns[$name] = $definition; if (isset($definition['primary']) && $definition['primary']) { $primary[] = $name; } } $options['foreignKeys'] = isset($this->_options['foreignKeys']) ? $this->_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. Cannot define constraint of different fields in the same table."); } continue; } $integrity = array('onUpdate' => $fk['onUpdate'], 'onDelete' => $fk['onDelete']); $fkName = $relation->getForeignKeyName(); if ($relation instanceof Doctrine_Relation_LocalKey) { $def = array('name' => $fkName, 'local' => $relation->getLocalColumnName(), 'foreign' => $relation->getForeignColumnName(), 'foreignTable' => $relation->getTable()->getTableName()); if ($integrity !== $emptyIntegrity) { $def = array_merge($def, $integrity); } if (($key = $this->_checkForeignKeyExists($def, $options['foreignKeys'])) === false) { $options['foreignKeys'][$fkName] = $def; } else { unset($def['name']); $options['foreignKeys'][$key] = array_merge($options['foreignKeys'][$key], $def); } } } } $options['primary'] = $primary; return array('tableName' => $this->getOption('tableName'), 'columns' => $columns, 'options' => array_merge($this->getOptions(), $options)); } /** * Check if a foreign definition already exists in the fks array for a * foreign table, local and foreign key * * @param array $def Foreign key definition to check for * @param array $foreignKeys Array of existing foreign key definitions to check in * @return boolean $result Whether or not the foreign key was found */ protected function _checkForeignKeyExists($def, $foreignKeys) { foreach ($foreignKeys as $key => $foreignKey) { if ($def['local'] == $foreignKey['local'] && $def['foreign'] == $foreignKey['foreign'] && $def['foreignTable'] == $foreignKey['foreignTable']) { return $key; } } return false; } /** * Retrieves the relation parser associated with this table. * * @return Doctrine_Relation_Parser relation parser object */ public function getRelationParser() { return $this->_parser; } /** * Magic method for accessing to object properties. * * This method is an alias for getOption. * * foreach ($table->indexes as $name => $definition) { * // ... * } * * * @param string $option * @return mixed */ public function __get($option) { if (isset($this->_options[$option])) { return $this->_options[$option]; } return null; } /** * Magic method for testing object properties existence. * * This method tests if an option exists. * * if (isset($table->tableName)) { * // ... * } * * * @param string $option */ public function __isset($option) { return isset($this->_options[$option]); } /** * Retrieves all options of this table and the associated values. * * @return array all options and their values */ public function getOptions() { return $this->_options; } /** * Sets all the options. * * This method sets options of the table that are specified in the argument. * It has no effect on other options. * * @param array $options keys are option names * @return void */ public function setOptions($options) { foreach ($options as $key => $value) { $this->setOption($key, $value); } } /** * Adds a foreignKey to the table in-memory definition. * * This method adds a foreign key to the schema definition. * It does not add the key to the physical table in the db; @see export(). * * @param array $definition definition of the foreign key * @return void */ public function addForeignKey(array $definition) { $this->_options['foreignKeys'][] = $definition; } /** * Adds a check constraint to the table in-memory definition. * * This method adds a CHECK constraint to the schema definition. * It does not add the constraint to the physical table in the * db; @see export(). * * @param $definition * @param mixed $name if string used as name for the constraint. * Otherwise it is indexed numerically. * @return void */ public function addCheckConstraint($definition, $name) { if (is_string($name)) { $this->_options['checks'][$name] = $definition; } else { $this->_options['checks'][] = $definition; } return $this; } /** * Adds an index to this table in-memory definition. * * This method adds an INDEX to the schema definition. * It does not add the index to the physical table in the db; @see export(). * * @param string $index index name * @param array $definition keys are type, fields * @return void */ public function addIndex($index, array $definition) { if (isset($definition['fields'])) { foreach ((array) $definition['fields'] as $key => $field) { if (is_numeric($key)) { $definition['fields'][$key] = $this->getColumnName($field); } else { $columnName = $this->getColumnName($key); unset($definition['fields'][$key]); $definition['fields'][$columnName] = $field; } } } $this->_options['indexes'][$index] = $definition; } /** * Retrieves an index definition. * * This method returns a given index definition: @see addIndex(). * * @param string $index index name; @see addIndex() * @return array|boolean array on success, FALSE on failure */ public function getIndex($index) { if (isset($this->_options['indexes'][$index])) { return $this->_options['indexes'][$index]; } return false; } /** * Defines a n-uple of fields that must be unique for every record. * * This method Will automatically add UNIQUE index definition * and validate the values on save. The UNIQUE index is not created in the * database until you use @see export(). * * @param array $fields values are fieldnames * @return void */ public function unique($fields) { $name = implode('_', $fields) . '_unqidx'; $definition = array('type' => 'unique', 'fields' => $fields); $this->addIndex($name, $definition); $this->_uniques[] = $fields; } /** * Adds a relation to the table. * * This method defines a relation on this table, that will be present on * every record belonging to this component. * * @param array $args first value is a string, name of related component; * second value is array, options for the relation. * @see Doctrine_Relation::_$definition * @param integer $type Doctrine_Relation::ONE or Doctrine_Relation::MANY * @return void * @todo Name proposal: addRelation */ public function bind($args, $type) { $options = ( ! isset($args[1])) ? array() : $args[1]; $options['type'] = $type; $this->_parser->bind($args[0], $options); } /** * Tests if a relation exists. * * This method queries the table definition to find out if a relation * is defined for this component. Alias defined with foreignAlias are not * recognized as there's only one Doctrine_Relation object on the owning * side. * * @param string $alias the relation alias to search for. * @return boolean true if the relation exists. Otherwise false. */ public function hasRelation($alias) { return $this->_parser->hasRelation($alias); } /** * Retrieves a relation object for this component. * * @param string $alias relation alias; @see hasRelation() * @return Doctrine_Relation */ public function getRelation($alias, $recursive = true) { return $this->_parser->getRelation($alias, $recursive); } /** * Retrieves all relation objects defined on this table. * * @return array */ public function getRelations() { return $this->_parser->getRelations(); } /** * Creates a query on this table. * * This method returns a new Doctrine_Query object and adds the component * name of this table as the query 'from' part. * * $table = Doctrine::getTable('User'); * $table->createQuery('myuser') * ->where('myuser.Phonenumber = ?', '5551234'); * * * @param string $alias name for component aliasing * @return Doctrine_Query */ public function createQuery($alias = '') { if ( ! empty($alias)) { $alias = ' ' . trim($alias); } return Doctrine_Query::create($this->_conn)->from($this->getComponentName() . $alias); } /** * Gets the internal record repository. * * @return Doctrine_Table_Repository */ public function getRepository() { return $this->_repository; } /** * Sets an option for the table. * * This method 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 */ public function setOption($name, $value) { switch ($name) { case 'name': case 'tableName': break; case 'enumMap': case 'inheritanceMap': case 'index': case 'treeOptions': if ( ! is_array($value)) { throw new Doctrine_Table_Exception($name . ' should be an array.'); } break; } $this->_options[$name] = $value; } /** * Returns the value of a given option. * * @see Doctrine_Table::$_options for available options * @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]; } return null; } /** * Returns a column name for a column alias. * * If the actual name for the alias cannot be found * this method returns the given alias. * * @param string $alias column alias * @return string column name */ public function getColumnName($fieldName) { // FIX ME: This is being used in places where an array is passed, but it should not be an array // For example in places where Doctrine should support composite foreign/primary keys $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName; if (isset($this->_columnNames[$fieldName])) { return $this->_columnNames[$fieldName]; } return strtolower($fieldName); } /** * Retrieves a column definition from this table schema. * * @param string $columnName * @return array column definition; @see $_columns */ public function getColumnDefinition($columnName) { if ( ! isset($this->_columns[$columnName])) { return false; } return $this->_columns[$columnName]; } /** * Returns a column alias for a column name. * * If no alias can be found the column name is returned. * * @param string $columnName column name * @return string column alias */ public function getFieldName($columnName) { if (isset($this->_fieldNames[$columnName])) { return $this->_fieldNames[$columnName]; } return $columnName; } public function setColumns(array $definitions) { foreach ($definitions as $name => $options) { $this->setColumn($name, $options['type'], $options['length'], $options); } } /** * Adds a column to the schema. * * This method does not alter the database table; @see export(); * * @see $_columns; * @param string $name column physical name * @param string $type type of data * @param integer $length maximum 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 */ public function setColumn($name, $type = null, $length = null, $options = array(), $prepend = false) { if (is_string($options)) { $options = explode('|', $options); } foreach ($options as $k => $option) { if (is_numeric($k)) { if ( ! empty($option)) { $options[$option] = true; } unset($options[$k]); } } // extract column name & field name if (stripos($name, ' as ')) { if (strpos($name, ' as')) { $parts = explode(' as ', $name); } else { $parts = explode(' AS ', $name); } if (count($parts) > 1) { $fieldName = $parts[1]; } else { $fieldName = $parts[0]; } $name = strtolower($parts[0]); } else { $fieldName = $name; $name = strtolower($name); } $name = trim($name); $fieldName = trim($fieldName); 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; } $defaultOptions = $this->getAttribute(Doctrine::ATTR_DEFAULT_COLUMN_OPTIONS); if (isset($defaultOptions['length']) && $defaultOptions['length'] && $length == null) { $length = $defaultOptions['length']; } if ($length == null) { switch ($type) { case 'integer': $length = 8; break; case 'decimal': $length = 18; break; case 'string': case 'clob': case 'float': case 'integer': case 'array': case 'object': case 'blob': case 'gzip': //$length = 2147483647; //All the DataDict driver classes have work-arounds to deal //with unset lengths. $length = null; 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; } } $options['type'] = $type; $options['length'] = $length; foreach ($defaultOptions as $key => $value) { if ( ! array_key_exists($key, $options) || is_null($options[$key])) { $options[$key] = $value; } } if ($prepend) { $this->_columns = array_merge(array($name => $options), $this->_columns); } else { $this->_columns[$name] = $options; } if (isset($options['primary']) && $options['primary']) { if (isset($this->_identifier)) { $this->_identifier = (array) $this->_identifier; } if ( ! in_array($fieldName, $this->_identifier)) { $this->_identifier[] = $fieldName; } } if (isset($options['default'])) { $this->hasDefaultValues = true; } } /** * Finds out whether this table has default values for columns. * * @return boolean */ public function hasDefaultValues() { return $this->hasDefaultValues; } /** * Retrieves the default value (if any) for a given column. * * @param string $fieldName column name * @return mixed default value as set in definition */ public function getDefaultValueOf($fieldName) { $columnName = $this->getColumnName($fieldName); if ( ! isset($this->_columns[$columnName])) { throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist."); } if (isset($this->_columns[$columnName]['default'])) { return $this->_columns[$columnName]['default']; } else { return null; } } /** * Returns the definition of the identifier key. * @return string can be array if a multi-column primary key is used. */ public function getIdentifier() { return $this->_identifier; } /** * Retrieves the type of primary key. * * This method finds out if the primary key is multifield. * @see Doctrine_Identifier constants * @return integer */ public function getIdentifierType() { return $this->_identifierType; } /** * Finds out whether the table definition contains a given column. * @param string $columnName * @return boolean */ public function hasColumn($columnName) { return isset($this->_columns[strtolower($columnName)]); } /** * Finds out whether the table definition has a given field. * * This method returns true if @see hasColumn() returns true or if an alias * named $fieldName exists. * @param string $fieldName * @return boolean */ public function hasField($fieldName) { return isset($this->_columnNames[$fieldName]); } /** * Sets the default connection for this table. * * This method assign the connection which this table will use * to create queries. * * @params Doctrine_Connection a connection object * @return Doctrine_Table this object; fluent interface */ public function setConnection(Doctrine_Connection $conn) { $this->_conn = $conn; $this->setParent($this->_conn); return $this; } /** * Returns the connection associated with this table (if any). * * @return Doctrine_Connection|null the connection object */ public function getConnection() { return $this->_conn; } /** * Creates a new record. * * This method create a new instance of the model defined by this table. * The class of this record is the subclass of Doctrine_Record defined by * this component. The record is not created in the database until you * call @save(). * * @param $array an array where keys are field names and * values representing field values. Can contain * also related components; * @see Doctrine_Record::fromArray() * @return Doctrine_Record the created record object */ public function create(array $array = array()) { $record = new $this->_options['name']($this, true); $record->fromArray($array); return $record; } /** * Adds a named query in the query registry. * * This methods register a query object with a name to use in the future. * @see createNamedQuery() * @param $queryKey query key name to use for storage * @param string|Doctrine_Query $query DQL string or object * @return void */ public function addNamedQuery($queryKey, $query) { $registry = Doctrine_Manager::getInstance()->getQueryRegistry(); $registry->add($this->getComponentName() . '/' . $queryKey, $query); } /** * Creates a named query from one in the query registry. * * This method clones a new query object from a previously registered one. * * @see addNamedQuery() * @param string $queryKey query key name * @return Doctrine_Query */ public function createNamedQuery($queryKey) { $queryRegistry = Doctrine_Manager::getInstance()->getQueryRegistry(); if (strpos($queryKey, '/') !== false) { $e = explode('/', $queryKey); return $queryRegistry->get($e[1], $e[0]); } return $queryRegistry->get($queryKey, $this->getComponentName()); } /** * Finds a record by its identifier. * * * $table->find(11); * $table->find(11, Doctrine::HYDRATE_RECORD); * $table->find('namedQueryForYearArchive', array(2009), Doctrine::HYDRATE_ARRAY); * * * @param mixed $name Database Row ID or Query Name defined previously as a NamedQuery * @param mixed $params This argument is the hydration mode (Doctrine::HYDRATE_ARRAY or * Doctrine::HYDRATE_RECORD) if first param is a Database Row ID. * Otherwise this argument expect an array of query params. * @param int $hydrationMode Optional Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD if * first argument is a NamedQuery * @return mixed Doctrine_Collection, array, Doctrine_Record or false if no result */ public function find() { $num_args = func_num_args(); // Named Query or IDs $name = func_get_arg(0); if (is_null($name)) { return false; } $ns = $this->getComponentName(); $m = $name; // Check for possible cross-access if ( ! is_array($name) && strpos($name, '/') !== false) { list($ns, $m) = explode('/', $name); } // Define query to be used if ( ! is_array($name) && Doctrine_Manager::getInstance()->getQueryRegistry()->has($m, $ns) ) { // We're dealing with a named query $q = $this->createNamedQuery($name); // Parameters construction $params = ($num_args >= 2) ? func_get_arg(1) : array(); // Hydration mode $hydrationMode = ($num_args == 3) ? func_get_arg(2) : null; // Executing query $res = $q->execute($params, $hydrationMode); } else { // We're passing a single ID or an array of IDs $q = $this->createQuery('dctrn_find') ->where('dctrn_find.' . implode(' = ? AND dctrn_find.', (array) $this->getIdentifier()) . ' = ?') ->limit(1); // Parameters construction $params = is_array($name) ? array_values($name) : array($name); // Hydration mode $hydrationMode = ($num_args == 2) ? func_get_arg(1) : null; // Executing query $res = $q->fetchOne($params, $hydrationMode); } $q->free(); return $res; } /** * Retrieves all the records stored in this table. * * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @return Doctrine_Collection|array */ public function findAll($hydrationMode = null) { return $this->createQuery('dctrn_find') ->execute(array(), $hydrationMode); } /** * Finds records in this table with a given SQL where clause. * * @param string $dql DQL WHERE clause to use * @param array $params query parameters (a la PDO) * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @return Doctrine_Collection|array * * @todo This actually takes DQL, not SQL, but it requires column names * instead of field names. This should be fixed to use raw SQL instead. */ public function findBySql($dql, $params = array(), $hydrationMode = null) { return $this->createQuery('dctrn_find') ->where($dql)->execute($params, $hydrationMode); } /** * Finds records in this table with a given DQL where clause. * * @param string $dql DQL WHERE clause * @param array $params preparated statement parameters * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @return Doctrine_Collection|array */ public function findByDql($dql, $params = array(), $hydrationMode = null) { $parser = new Doctrine_Query($this->_conn); $component = $this->getComponentName(); $query = 'FROM ' . $component . ' dctrn_find WHERE ' . $dql; return $parser->query($query, $params, $hydrationMode); } /** * Find records basing on a field. * * @param string $column field for the WHERE clause * @param string $value prepared statement parameter * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @return Doctrine_Collection|array */ protected function findBy($fieldName, $value, $hydrationMode = null) { return $this->createQuery('dctrn_find') ->where('dctrn_find.' . $fieldName . ' = ?', array($value)) ->execute(array(), $hydrationMode); } /** * Finds the first record that satisfy the clause. * * @param string $column field for the WHERE clause * @param string $value prepared statement parameter * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @return Doctrine_Record */ protected function findOneBy($fieldName, $value, $hydrationMode = null) { return $this->createQuery('dctrn_find') ->where('dctrn_find.' . $fieldName . ' = ?', array($value)) ->limit(1) ->fetchOne(array(), $hydrationMode); } /** * Finds result of a named query. * * This method fetches data using the provided $queryKey to choose a named * query in the query registry. * * @param string $queryKey the query key * @param array $params prepared statement params (if any) * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @throws Doctrine_Query_Registry if no query for given queryKey is found * @return Doctrine_Collection|array */ public function execute($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD) { return $this->createNamedQuery($queryKey)->execute($params, $hydrationMode); } /** * Fetches one record with a named query. * * This method uses the provided $queryKey to clone and execute * the associated named query in the query registry. * * @param string $queryKey the query key * @param array $params prepared statement params (if any) * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD * @throws Doctrine_Query_Registry if no query for given queryKey is found * @return Doctrine_Record|array */ public function executeOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD) { return $this->createNamedQuery($queryKey)->fetchOne($params, $hydrationMode); } /** * Clears the first level cache (identityMap). * * This method ensures that records are reloaded from the db. * * @return void * @todo what about a more descriptive name? clearIdentityMap? */ public function clear() { $this->_identityMap = array(); } /** * Adds a record to the first level cache (identity map). * * This method is used internally to cache records, ensuring that only one * object that represents a sql record exists in all scopes. * * @param Doctrine_Record $record record to be added * @return boolean true if record was not present in the map * @todo Better name? registerRecord? */ public function addRecord(Doctrine_Record $record) { $id = implode(' ', $record->identifier()); if (isset($this->_identityMap[$id])) { return false; } $this->_identityMap[$id] = $record; return true; } /** * Removes a record from the identity map. * * This method deletes from the cache the given record; can be used to * force reloading of an object from database. * * @param Doctrine_Record $record record to remove from cache * @return boolean true if the record was found and removed, * false if the record wasn't found. */ public function removeRecord(Doctrine_Record $record) { $id = implode(' ', $record->identifier()); if (isset($this->_identityMap[$id])) { unset($this->_identityMap[$id]); return true; } return false; } /** * Returns a new record. * * This method checks if a internal record exists in identityMap, if does * not exist it creates a new one. * * @return Doctrine_Record */ public function getRecord() { if ( ! empty($this->_data)) { $identifierFieldNames = $this->getIdentifier(); if ( ! is_array($identifierFieldNames)) { $identifierFieldNames = array($identifierFieldNames); } $found = false; foreach ($identifierFieldNames as $fieldName) { if ( ! isset($this->_data[$fieldName])) { // primary key column not found return new record $found = true; break; } $id[] = $this->_data[$fieldName]; } if ($found) { $recordName = $this->getComponentName(); $record = new $recordName($this, true); $this->_data = array(); return $record; } $id = implode(' ', $id); if (isset($this->_identityMap[$id])) { $record = $this->_identityMap[$id]; if ($record->getTable()->getAttribute(Doctrine::ATTR_HYDRATE_OVERWRITE)) { $record->hydrate($this->_data); if ($record->state() == Doctrine_Record::STATE_PROXY) { if (count($this->_data) >= $this->getColumnCount()) { $record->state(Doctrine_Record::STATE_CLEAN); } } } else { $record->hydrate($this->_data, false); } } else { $recordName = $this->getComponentName(); $record = new $recordName($this); $this->_identityMap[$id] = $record; } $this->_data = array(); } else { $recordName = $this->getComponentName(); $record = new $recordName($this, true); } return $record; } /** * Get the classname to return. Most often this is just the options['name']. * * Check the subclasses option and the inheritanceMap for each subclass to see * if all the maps in a subclass is met. If this is the case return that * subclass name. If no subclasses match or if there are no subclasses defined * return the name of the class for this tables record. * * @todo this function could use reflection to check the first time it runs * if the subclassing option is not set. * * @return string The name of the class to create * @deprecated */ public function getClassnameToReturn() { if ( ! isset($this->_options['subclasses'])) { return $this->_options['name']; } foreach ($this->_options['subclasses'] as $subclass) { $table = $this->_conn->getTable($subclass); $inheritanceMap = $table->getOption('inheritanceMap'); $nomatch = false; foreach ($inheritanceMap as $key => $value) { if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) { $nomatch = true; break; } } if ( ! $nomatch) { return $table->getComponentName(); } } return $this->_options['name']; } /** * @param $id database row id * @throws Doctrine_Find_Exception * @return Doctrine_Record */ final public function getProxy($id = null) { if ($id !== null) { $identifierColumnNames = $this->getIdentifierColumnNames(); $query = 'SELECT ' . implode(', ', (array) $identifierColumnNames) . ' FROM ' . $this->getTableName() . ' WHERE ' . implode(' = ? && ', (array) $identifierColumnNames) . ' = ?'; $query = $this->applyInheritance($query); $params = array_merge(array($id), array_values($this->_options['inheritanceMap'])); $this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC); if ($this->_data === false) return false; } return $this->getRecord(); } /** * applyInheritance * @param $where query where part to be modified * @return string query where part with column aggregation inheritance added */ final public function applyInheritance($where) { if ( ! empty($this->_options['inheritanceMap'])) { $a = array(); foreach ($this->_options['inheritanceMap'] as $field => $value) { $a[] = $this->getColumnName($field) . ' = ?'; } $i = implode(' AND ', $a); $where .= ' AND ' . $i; } return $where; } /** * Implements Countable interface. * * @return integer number of records in the table */ public function count() { return $this->createQuery()->count(); } /** * @return Doctrine_Query a Doctrine_Query object */ public function getQueryObject() { $graph = new Doctrine_Query($this->getConnection()); $graph->load($this->getComponentName()); return $graph; } /** * Retrieves the enum values for a given field. * * @param string $fieldName * @return array */ public function getEnumValues($fieldName) { $columnName = $this->getColumnName($fieldName); if (isset($this->_columns[$columnName]['values'])) { return $this->_columns[$columnName]['values']; } else { return array(); } } /** * Retrieves an enum value. * * This method finds a enum string value. If ATTR_USE_NATIVE_ENUM is set * on the connection, index and value are the same thing. * * @param string $fieldName * @param integer $index numeric index of the enum * @return mixed */ public function enumValue($fieldName, $index) { if ($index instanceof Doctrine_Null) { return $index; } $columnName = $this->getColumnName($fieldName); if ( ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM) && isset($this->_columns[$columnName]['values'][$index]) ) { return $this->_columns[$columnName]['values'][$index]; } return $index; } /** * Retrieves an enum index. * @see enumValue() * * @param string $fieldName * @param mixed $value value of the enum considered * @return integer can be string if native enums are used. */ 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; } /** * Validates a given field using table ATTR_VALIDATE rules. * @see Doctrine::ATTR_VALIDATE * * @param string $fieldName * @param string $value * @param Doctrine_Record $record record to consider; if it does not exists, it is created * @return Doctrine_Validator_ErrorStack $errorStack */ public function validateField($fieldName, $value, Doctrine_Record $record = null) { if ($record instanceof Doctrine_Record) { $errorStack = $record->getErrorStack(); } else { $record = $this->create(); $errorStack = new Doctrine_Validator_ErrorStack($this->getOption('name')); } if ($value === self::$_null) { $value = null; } else if ($value instanceof Doctrine_Record && $value->exists()) { $value = $value->getIncremented(); } else if ($value instanceof Doctrine_Record && ! $value->exists()) { foreach($this->getRelations() as $relation) { if ($fieldName == $relation->getLocalFieldName() && (get_class($value) == $relation->getClass() || is_subclass_of($value, $relation->getClass()))) { return $errorStack; } } } $dataType = $this->getTypeOf($fieldName); // Validate field type, if type validation is enabled if ($this->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_TYPES) { if ( ! Doctrine_Validator::isValidType($value, $dataType)) { $errorStack->add($fieldName, 'type'); } if ($dataType == 'enum') { $enumIndex = $this->enumIndex($fieldName, $value); if ($enumIndex === false && $value !== null) { $errorStack->add($fieldName, 'enum'); } } } // Validate field length, if length validation is enabled if ($this->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_LENGTHS) { if ( ! Doctrine_Validator::validateLength($value, $dataType, $this->getFieldLength($fieldName))) { $errorStack->add($fieldName, 'length'); } } // Run all custom validators foreach ($this->getFieldValidators($fieldName) as $validatorName => $args) { if ( ! is_string($validatorName)) { $validatorName = $args; $args = array(); } $validator = Doctrine_Validator::getValidator($validatorName); $validator->invoker = $record; $validator->field = $fieldName; $validator->args = $args; if ( ! $validator->validate($value)) { $errorStack->add($fieldName, $validator); } } return $errorStack; } /** * Validates all the unique indexes. * * This methods validates 'unique' sets of fields for the given Doctrine_Record instance. * Pushes error to the record error stack if they are generated. * * @param Doctrine_Record $record */ public function validateUniques(Doctrine_Record $record) { $errorStack = $record->getErrorStack(); $validator = Doctrine_Validator::getValidator('unique'); $validator->invoker = $record; foreach ($this->_uniques as $fields) { $validator->field = $fields; $values = array(); foreach ($fields as $field) { $values[] = $record->$field; } if ( ! $validator->validate($values)) { foreach ($fields as $field) { $errorStack->add($field, $validator); } } } } /** * @return integer the number of columns in this table */ public function getColumnCount() { return $this->columnCount; } /** * Retrieves all columns of the table. * * @see $_columns; * @return array keys are column names and values are definition */ public function getColumns() { return $this->_columns; } /** * Removes a field name from the table schema information. * * @param string $fieldName * @return boolean true if the field is found and removed. * False otherwise. */ public function removeColumn($fieldName) { if ( ! $this->hasField($fieldName)) { return false; } $columnName = $this->getColumnName($fieldName); unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]); $this->columnCount = count($this->_columns); return true; } /** * Returns an array containing all the column names. * * @return array numeric array */ public function getColumnNames(array $fieldNames = null) { if ($fieldNames === null) { return array_keys($this->_columns); } else { $columnNames = array(); foreach ($fieldNames as $fieldName) { $columnNames[] = $this->getColumnName($fieldName); } return $columnNames; } } /** * Returns an array with all the identifier column names. * * @return array numeric array */ public function getIdentifierColumnNames() { return $this->getColumnNames((array) $this->getIdentifier()); } /** * Gets the array of unique fields sets. * @see $_uniques; * * @return array numeric array */ public function getUniques() { return $this->_uniques; } /** * Returns an array containing all the field names. * * @return array numeric array */ public function getFieldNames() { return array_values($this->_fieldNames); } /** * Retrieves the definition of a field. * * This method retrieves the definition of the column, basing of $fieldName * which can be a column name or a field name (alias). * * @param string $fieldName * @return array false on failure */ public function getDefinitionOf($fieldName) { $columnName = $this->getColumnName($fieldName); return $this->getColumnDefinition($columnName); } /** * Retrieves the type of a field. * * @param string $fieldName * @return string false on failure */ public function getTypeOf($fieldName) { return $this->getTypeOfColumn($this->getColumnName($fieldName)); } /** * Retrieves the type of a column. * * @param string $columnName * @return string false if column is not found */ public function getTypeOfColumn($columnName) { return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false; } /** * Doctrine uses this function internally. * Users are strongly discouraged to use this function. * * @access private * @param array $data internal data * @return void */ public function setData(array $data) { $this->_data = $data; } /** * Returns internal data. * * This method is used by Doctrine_Record instances * when retrieving data from database. * * @return array */ public function getData() { return $this->_data; } /** * Performs special data preparation. * * This method returns a representation of a field data, 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 Type hint used to pass in the type of the value to prepare * if it is already known. This enables the method to skip * the type determination. Used i.e. during hydration. * @return mixed prepared value */ public function prepareValue($fieldName, $value, $typeHint = null) { if ($value === self::$_null) { return self::$_null; } else if ($value === null) { return null; } else { $type = is_null($typeHint) ? $this->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->enumValue($fieldName, $value); break; case 'boolean': return (boolean) $value; break; case 'array': case 'object': if (is_string($value)) { $value = empty($value) ? null:unserialize($value); if ($value === false) { throw new Doctrine_Table_Exception('Unserialization of ' . $fieldName . ' failed.'); } return $value; } break; case 'gzip': $value = gzuncompress($value); if ($value === false) { throw new Doctrine_Table_Exception('Uncompressing of ' . $fieldName . ' failed.'); } return $value; break; } } return $value; } /** * Gets associated tree. * This method returns the associated Tree object (if any exists). * Normally implemented by NestedSet behavior. * * @return Doctrine_Tree false if not a tree */ public function getTree() { if (isset($this->_options['treeImpl'])) { if ( ! $this->_tree) { $options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array(); $this->_tree = Doctrine_Tree::factory($this, $this->_options['treeImpl'], $options ); } return $this->_tree; } return false; } /** * Gets the subclass of Doctrine_Record that belongs to this table. * * @return string */ public function getComponentName() { return $this->_options['name']; } /** * Gets the table name in the db. * * @return string */ public function getTableName() { return $this->_options['tableName']; } /** * sets the table name in the schema definition. * * @param string $tableName * @return void */ public function setTableName($tableName) { $this->setOption('tableName', $this->_conn->formatter->getTableName($tableName)); } /** * Determines if table acts as tree. * * @return boolean if tree return true, otherwise returns false */ public function isTree() { return ( ! is_null($this->_options['treeImpl'])) ? true : false; } /** * Retrieves all templates (behaviors) attached to this table. * * @return array an array containing all templates */ public function getTemplates() { return $this->_templates; } /** * Retrieves a particular template by class name. * * This method retrieves a behavior/template object attached to the table. * For Doctrine_Template_* classes, the base name can be used. * * @param string $template name of the behavior * @throws Doctrine_Table_Exception if the given template is * not set on this table * @return Doctrine_Template */ public function getTemplate($template) { if (isset($this->_templates['Doctrine_Template_' . $template])) { return $this->_templates['Doctrine_Template_' . $template]; } else if (isset($this->_templates[$template])) { return $this->_templates[$template]; } throw new Doctrine_Table_Exception('Template ' . $template . ' not loaded'); } /** * Checks if the table has a given template. * * @param string $template name of template; @see getTemplate() * @return boolean */ public function hasTemplate($template) { return isset($this->_templates[$template]) || isset($this->_templates['Doctrine_Template_' . $template]); } /** * Adds a template to this table. * * @param string $template template name * @param Doctrine_Template $impl behavior to attach * @return Doctrine_Table */ public function addTemplate($template, Doctrine_Template $impl) { $this->_templates[$template] = $impl; return $this; } /** * Gets all the generators for this table. * * @return array $generators */ public function getGenerators() { return $this->_generators; } /** * Gets generator instance for a passed name. * * @param string $generator * @return Doctrine_Record_Generator $generator */ public function getGenerator($generator) { if ( ! isset($this->_generators[$generator])) { throw new Doctrine_Table_Exception('Generator ' . $generator . ' not loaded'); } return $this->_generators[$generator]; } /** * Checks if a generator name exists. * * @param string $generator * @return void */ public function hasGenerator($generator) { return isset($this->_generators[$generator]); } /** * Adds a generate to the table instance. * * @param Doctrine_Record_Generator $generator * @param string $name * @return Doctrine_Table */ public function addGenerator(Doctrine_Record_Generator $generator, $name = null) { if ($name === null) { $this->_generators[] = $generator; } else { $this->_generators[$name] = $generator; } return $this; } /** * Binds query parts to this component. * @see bindQueryPart() * * @param array $queryParts an array of pre-bound query parts * @return Doctrine_Table this object */ public function bindQueryParts(array $queryParts) { $this->_options['queryParts'] = $queryParts; return $this; } /** * Adds default query parts to the selects executed on this table. * * This method binds given value to given query part. * Every query created by this table will have this part set by default. * * @param string $queryPart * @param mixed $value * @return Doctrine_Record this object */ public function bindQueryPart($queryPart, $value) { $this->_options['queryParts'][$queryPart] = $value; return $this; } /** * Gets the names of all validators being applied on a field. * * @param string $fieldName * @return array names of validators */ public function getFieldValidators($fieldName) { $validators = array(); $columnName = $this->getColumnName($fieldName); // this loop is a dirty workaround to get the validators filtered out of // the options, since everything is squeezed together currently foreach ($this->_columns[$columnName] as $name => $args) { if (empty($name) || $name == 'primary' || $name == 'protected' || $name == 'autoincrement' || $name == 'default' || $name == 'values' || $name == 'sequence' || $name == 'zerofill' || $name == 'owner' || $name == 'scale' || $name == 'type' || $name == 'length' || $name == 'fixed' || $name == 'comment' || $name == 'extra') { continue; } if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement']) && $this->_columns[$columnName]['autoincrement'] === true) { continue; } // skip it if it's explicitly set to FALSE (i.e. notnull => false) if ($args === false) { continue; } $validators[$name] = $args; } return $validators; } /** * Gets the maximum length of a field. * For integer fields, length is bytes occupied. * For decimal fields, it is the total number of cyphers * * @param string $fieldName * @return integer */ public function getFieldLength($fieldName) { return $this->_columns[$this->getColumnName($fieldName)]['length']; } /** * Retrieves a bound query part. * @see bindQueryPart() * * @param string $queryPart field interested * @return string value of the bind */ public function getBoundQueryPart($queryPart) { if ( ! isset($this->_options['queryParts'][$queryPart])) { return null; } return $this->_options['queryParts'][$queryPart]; } /** * unshiftFilter * * @param Doctrine_Record_Filter $filter * @return Doctrine_Table this object (provides a fluent interface) */ public function unshiftFilter(Doctrine_Record_Filter $filter) { $filter->setTable($this); $filter->init(); array_unshift($this->_filters, $filter); return $this; } /** * getFilters * * @return array $filters */ public function getFilters() { return $this->_filters; } /** * Generates a string representation of this object. * * This method is useful for debugging purposes, or it can be overriden in * Doctrine_Record to provide a value when Record is casted to (string). * * @return string */ public function __toString() { return Doctrine_Lib::getTableAsString($this); } /** * Resolves the passed find by field name inflecting the parameter. * * This method resolves the appropriate field name * regardless of whether the user passes a column name, field name, or a Doctrine_Inflector::classified() * version of their column name. It will be inflected with Doctrine_Inflector::tableize() * to get the column or field name. * * @param string $name * @return string $fieldName */ protected function _resolveFindByFieldName($name) { $fieldName = Doctrine_Inflector::tableize($name); if ($this->hasColumn($name) || $this->hasField($name)) { return $this->getFieldName($this->getColumnName($name)); } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) { return $this->getFieldName($this->getColumnName($fieldName)); } else { return false; } } /** * Adds support for magic finders. * * This method add support for calling methods not defined in code, such as: * findByColumnName, findByRelationAlias * findById, findByContactId, etc. * * @return the result of the finder */ public function __call($method, $arguments) { $lcMethod = strtolower($method); if (substr($lcMethod, 0, 6) == 'findby') { $by = substr($method, 6, strlen($method)); $method = 'findBy'; } else if (substr($lcMethod, 0, 9) == 'findoneby') { $by = substr($method, 9, strlen($method)); $method = 'findOneBy'; } if (isset($by)) { if ( ! isset($arguments[0])) { throw new Doctrine_Table_Exception('You must specify the value to findBy'); } $fieldName = $this->_resolveFindByFieldName($by); $hydrationMode = isset($arguments[1]) ? $arguments[1]:null; if ($this->hasField($fieldName)) { return $this->$method($fieldName, $arguments[0], $hydrationMode); } else if ($this->hasRelation($by)) { $relation = $this->getRelation($by); if ($relation['type'] === Doctrine_Relation::MANY) { throw new Doctrine_Table_Exception('Cannot findBy many relationship.'); } return $this->$method($relation['local'], $arguments[0], $hydrationMode); } else { throw new Doctrine_Table_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.'); } } // Forward the method on to the record instance and see if it has anything or one of its behaviors try { return call_user_func_array(array($this->getRecordInstance(), $method . 'TableProxy'), $arguments); } catch (Doctrine_Record_UnknownPropertyException $e) {} throw new Doctrine_Table_Exception(sprintf('Unknown method %s::%s', get_class($this), $method)); } }