3
* $Id: Table.php 5608 2009-03-17 18:00:18Z jwage $
5
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17
* This software consists of voluntary contributions made by many individuals
18
* and is licensed under the LGPL. For more information, see
19
* <http://www.phpdoctrine.org>.
23
* Doctrine_Table represents a database table
24
* each Doctrine_Table holds the information of foreignKeys and associations
27
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
30
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
31
* @version $Revision: 5608 $
32
* @link www.phpdoctrine.org
35
class Doctrine_Table extends Doctrine_Configurable implements Countable
38
* @var array $data temporary data which is then loaded into Doctrine_Record::$_data
40
protected $_data = array();
43
* @var mixed $identifier The field names of all fields that are part of the identifier/primary key
45
protected $_identifier = array();
48
* @see Doctrine_Identifier constants
49
* @var integer $identifierType the type of identifier this table uses
51
protected $_identifierType;
54
* @var Doctrine_Connection $conn Doctrine_Connection object that created this table
59
* @var array $identityMap first level cache
61
protected $_identityMap = array();
64
* @var Doctrine_Table_Repository $repository record repository
66
protected $_repository;
69
* @var array $columns an array of column definitions,
70
* keys are column names and values are column definitions
72
* the definition array has atleast the following values:
74
* -- type the column type, eg. 'integer'
75
* -- length the column length, eg. 11
78
* -- notnull whether or not the column is marked as notnull
79
* -- values enum values
80
* -- notblank notblank validator + notnull constraint
83
protected $_columns = array();
86
* @var array $_fieldNames an array of field names. used to look up field names
88
* keys are column names and values are field names
90
protected $_fieldNames = array();
94
* @var array $_columnNames an array of column names
95
* keys are field names and values column names.
96
* used to look up column names from field names.
97
* this is the reverse lookup map of $_fieldNames.
99
protected $_columnNames = array();
102
* @var integer $columnCount cached column count, Doctrine_Record uses this column count in when
103
* determining its state
105
protected $columnCount;
108
* @var boolean $hasDefaultValues whether or not this table has default values
110
protected $hasDefaultValues;
113
* @var array $options an array containing all options
115
* -- name name of the component, for example component name of the GroupTable is 'Group'
117
* -- parents the parent classes of this component
119
* -- declaringClass name of the table definition declaring class (when using inheritance the class
120
* that defines the table structure can be any class in the inheritance hierarchy,
121
* hence we need reflection to check out which class actually calls setTableDefinition)
123
* -- tableName database table name, in most cases this is the same as component name but in some cases
124
* where one-table-multi-class inheritance is used this will be the name of the inherited table
126
* -- sequenceName Some databases need sequences instead of auto incrementation primary keys,
127
* you can set specific sequence for your table by calling setOption('sequenceName', $seqName)
128
* where $seqName is the name of the desired sequence
130
* -- enumMap enum value arrays
132
* -- inheritanceMap inheritanceMap is used for inheritance mapping, keys representing columns and values
133
* the column values that should correspond to child classes
135
* -- type table type (mysql example: INNODB)
137
* -- charset character set
139
* -- foreignKeys the foreign keys of this table
141
* -- checks the check constraints of this table, eg. 'price > dicounted_price'
143
* -- collate collate attribute
145
* -- indexes the index definitions of this table
147
* -- treeImpl the tree implementation of this table (if any)
149
* -- treeOptions the tree options
151
* -- queryParts the bound query parts
155
protected $_options = array('name' => null,
157
'sequenceName' => null,
158
'inheritanceMap' => array(),
159
'enumMap' => array(),
164
'treeOptions' => array(),
165
'indexes' => array(),
166
'parents' => array(),
167
'joinedParents' => array(),
168
'queryParts' => array(),
169
'versioning' => null,
170
'subclasses' => array(),
174
* @var Doctrine_Tree $tree tree object associated with this table
179
* @var Doctrine_Relation_Parser $_parser relation parser object
184
* @see Doctrine_Template
185
* @var array $_templates an array containing all templates attached to this table
187
protected $_templates = array();
190
* @see Doctrine_Record_Filter
191
* @var array $_filters an array containing all record filters attached to this table
193
protected $_filters = array();
196
* @see Doctrine_Record_Generator
197
* @var array $_generators an array containing all generators attached to this table
199
protected $_generators = array();
202
* @var array $_invokedMethods method invoker cache
204
protected $_invokedMethods = array();
207
* @var Doctrine_Record $record empty instance of the given model
214
* @throws Doctrine_Connection_Exception if there are no opened connections
215
* @param string $name the name of the component
216
* @param Doctrine_Connection $conn the connection associated with this table
218
public function __construct($name, Doctrine_Connection $conn, $initDefinition = false)
220
$this->_conn = $conn;
222
$this->setParent($this->_conn);
224
$this->_options['name'] = $name;
225
$this->_parser = new Doctrine_Relation_Parser($this);
227
if ($initDefinition) {
228
$this->record = $this->initDefinition();
230
$this->initIdentifier();
232
$this->record->setUp();
234
// if tree, set up tree
235
if ($this->isTree()) {
236
$this->getTree()->setUp();
239
if ( ! isset($this->_options['tableName'])) {
240
$this->setTableName(Doctrine_Inflector::tableize($this->_options['name']));
243
$this->_filters[] = new Doctrine_Record_Filter_Standard();
244
$this->_repository = new Doctrine_Table_Repository($this);
251
* Empty template method to provide concrete Table classes with the possibility
252
* to hook into the constructor procedure
256
public function construct()
260
* Initializes the in-memory table definition.
262
* @param string $name
264
public function initDefinition()
266
$name = $this->_options['name'];
267
if ( ! class_exists($name) || empty($name)) {
268
throw new Doctrine_Exception("Couldn't find class " . $name);
270
$record = new $name($this);
276
// get parent classes
279
if ($class === 'Doctrine_Record') {
285
} while ($class = get_parent_class($class));
287
if ($class === false) {
288
throw new Doctrine_Table_Exception('Class "' . $name . '" must be a child class of Doctrine_Record');
292
$names = array_reverse($names);
295
$this->_options['parents'] = $names;
297
// create database table
298
if (method_exists($record, 'setTableDefinition')) {
299
$record->setTableDefinition();
300
// get the declaring class of setTableDefinition method
301
$method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
302
$class = $method->getDeclaringClass();
305
$class = new ReflectionClass($class);
308
$this->_options['joinedParents'] = array();
310
foreach (array_reverse($this->_options['parents']) as $parent) {
312
if ($parent === $class->getName()) {
315
$ref = new ReflectionClass($parent);
317
if ($ref->isAbstract() || ! $class->isSubClassOf($parent)) {
320
$parentTable = $this->_conn->getTable($parent);
323
$parentColumns = $parentTable->getColumns();
325
foreach ($parentColumns as $columnName => $definition) {
326
if ( ! isset($definition['primary']) || $definition['primary'] === false) {
327
if (isset($this->_columns[$columnName])) {
331
if ( ! isset($parentColumns[$columnName]['owner'])) {
332
$parentColumns[$columnName]['owner'] = $parentTable->getComponentName();
335
$this->_options['joinedParents'][] = $parentColumns[$columnName]['owner'];
338
unset($parentColumns[$columnName]);
346
foreach ($parentColumns as $columnName => $definition) {
347
$fullName = $columnName . ' as ' . $parentTable->getFieldName($columnName);
348
$this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true);
354
$this->_options['joinedParents'] = array_values(array_unique($this->_options['joinedParents']));
356
$this->_options['declaringClass'] = $class;
358
// set the table definition for the given tree implementation
359
if ($this->isTree()) {
360
$this->getTree()->setTableDefinition();
363
$this->columnCount = count($this->_columns);
365
if ( ! isset($this->_options['tableName'])) {
366
$this->setTableName(Doctrine_Inflector::tableize($class->getName()));
373
* Initializes the table identifier(s)/primary key(s)
377
public function initIdentifier()
379
switch (count($this->_identifier)) {
381
if ( ! empty($this->_options['joinedParents'])) {
382
$root = current($this->_options['joinedParents']);
384
$table = $this->_conn->getTable($root);
386
$this->_identifier = $table->getIdentifier();
388
$this->_identifierType = ($table->getIdentifierType() !== Doctrine::IDENTIFIER_AUTOINC)
389
? $table->getIdentifierType() : Doctrine::IDENTIFIER_NATURAL;
391
// add all inherited primary keys
392
foreach ((array) $this->_identifier as $id) {
393
$definition = $table->getDefinitionOf($id);
395
// inherited primary keys shouldn't contain autoinc
396
// and sequence definitions
397
unset($definition['autoincrement']);
398
unset($definition['sequence']);
400
// add the inherited primary key column
401
$fullName = $id . ' as ' . $table->getFieldName($id);
402
$this->setColumn($fullName, $definition['type'], $definition['length'],
406
$definition = array('type' => 'integer',
408
'autoincrement' => true,
410
$this->setColumn('id', $definition['type'], $definition['length'], $definition, true);
411
$this->_identifier = 'id';
412
$this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
414
$this->columnCount++;
417
foreach ($this->_identifier as $pk) {
418
$e = $this->getDefinitionOf($pk);
422
foreach ($e as $option => $value) {
427
$e2 = explode(':', $option);
429
switch (strtolower($e2[0])) {
430
case 'autoincrement':
432
if ($value !== false) {
433
$this->_identifierType = Doctrine::IDENTIFIER_AUTOINC;
439
$this->_identifierType = Doctrine::IDENTIFIER_SEQUENCE;
442
if (is_string($value)) {
443
$this->_options['sequenceName'] = $value;
445
if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) {
446
$this->_options['sequenceName'] = $sequence;
448
$this->_options['sequenceName'] = $this->_conn->formatter->getSequenceName($this->_options['tableName']);
454
if ( ! isset($this->_identifierType)) {
455
$this->_identifierType = Doctrine::IDENTIFIER_NATURAL;
459
$this->_identifier = $pk;
463
$this->_identifierType = Doctrine::IDENTIFIER_COMPOSITE;
468
* Gets the owner of a column.
469
* The owner of a column is the name of the component in a hierarchy that
470
* defines the column.
472
* @param string $columnName The column name
473
* @return string The name of the owning/defining component
475
public function getColumnOwner($columnName)
477
if (isset($this->_columns[$columnName]['owner'])) {
478
return $this->_columns[$columnName]['owner'];
480
return $this->getComponentName();
485
* Gets the record instance for this table. The Doctrine_Table instance always holds at least one
486
* instance of a model so that it can be reused for several things, but primarily it is first
487
* used to instantiate all the internal in memory meta data
489
* @return object Empty instance of the record
491
public function getRecordInstance()
493
if ( ! $this->record) {
494
$this->record = new $this->_options['name'];
496
return $this->record;
500
* Checks whether a column is inherited from a component further up in the hierarchy.
502
* @param $columnName The column name
503
* @return boolean TRUE if column is inherited, FALSE otherwise.
505
public function isInheritedColumn($columnName)
507
return (isset($this->_columns[$columnName]['owner']));
511
* Checks whether a field is part of the table identifier/primary key field(s).
513
* @param string $fieldName The field name
514
* @return boolean TRUE if the field is part of the table identifier/primary key field(s),
517
public function isIdentifier($fieldName)
519
return ($fieldName === $this->getIdentifier() ||
520
in_array($fieldName, (array) $this->getIdentifier()));
524
* Checks whether a field identifier is of type autoincrement
526
* @return boolean TRUE if the identifier is autoincrement
529
public function isIdentifierAutoincrement()
531
return $this->getIdentifierType() === Doctrine::IDENTIFIER_AUTOINC;
535
* Checks whether a field identifier is a composite key.
537
* @return boolean TRUE if the identifier is a composite key,
540
public function isIdentifierComposite()
542
return $this->getIdentifierType() === Doctrine::IDENTIFIER_COMPOSITE;
548
* @param string $method
551
public function getMethodOwner($method)
553
return (isset($this->_invokedMethods[$method])) ?
554
$this->_invokedMethods[$method] : false;
560
* @param string $method
561
* @param string $class
563
public function setMethodOwner($method, $class)
565
$this->_invokedMethods[$method] = $class;
570
* exports this table to database based on column and option definitions
572
* @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
573
* occurred during the create table operation
574
* @return boolean whether or not the export operation was successful
575
* false if table already existed in the database
577
public function export()
579
$this->_conn->export->exportTable($this);
583
* getExportableFormat
584
* returns exportable presentation of this object
588
public function getExportableFormat($parseForeignKeys = true)
593
foreach ($this->getColumns() as $name => $definition) {
595
if (isset($definition['owner'])) {
599
switch ($definition['type']) {
601
if (isset($definition['default'])) {
602
$definition['default'] = $this->getConnection()->convertBooleans($definition['default']);
606
$columns[$name] = $definition;
608
if (isset($definition['primary']) && $definition['primary']) {
613
$options['foreignKeys'] = isset($this->_options['foreignKeys']) ?
614
$this->_options['foreignKeys'] : array();
616
if ($parseForeignKeys && $this->getAttribute(Doctrine::ATTR_EXPORT)
617
& Doctrine::EXPORT_CONSTRAINTS) {
619
$constraints = array();
621
$emptyIntegrity = array('onUpdate' => null,
624
foreach ($this->getRelations() as $name => $relation) {
625
$fk = $relation->toArray();
626
$fk['foreignTable'] = $relation->getTable()->getTableName();
628
if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
629
if ($relation->hasConstraint()) {
630
throw new Doctrine_Table_Exception("Badly constructed integrity constraints. Cannot define constraint of different fields in the same table.");
635
$integrity = array('onUpdate' => $fk['onUpdate'],
636
'onDelete' => $fk['onDelete']);
638
if ($relation instanceof Doctrine_Relation_LocalKey) {
639
$def = array('local' => $relation->getLocalColumnName(),
640
'foreign' => $relation->getForeignColumnName(),
641
'foreignTable' => $relation->getTable()->getTableName());
643
if (($key = array_search($def, $options['foreignKeys'])) === false) {
644
$options['foreignKeys'][] = $def;
645
if ($integrity !== $emptyIntegrity) {
646
$constraints[] = $integrity;
649
if ($integrity !== $emptyIntegrity) {
650
$constraints[$key] = $integrity;
656
foreach ($constraints as $k => $def) {
657
$options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
661
$options['primary'] = $primary;
663
return array('tableName' => $this->getOption('tableName'),
664
'columns' => $columns,
665
'options' => array_merge($this->getOptions(), $options));
670
* return the relation parser associated with this table
672
* @return Doctrine_Relation_Parser relation parser object
674
public function getRelationParser()
676
return $this->_parser;
681
* an alias for getOption
683
* @param string $option
685
public function __get($option)
687
if (isset($this->_options[$option])) {
688
return $this->_options[$option];
696
* @param string $option
698
public function __isset($option)
700
return isset($this->_options[$option]);
705
* returns all options of this table and the associated values
707
* @return array all options and their values
709
public function getOptions()
711
return $this->_options;
717
* @param string $options
720
public function setOptions($options)
722
foreach ($options as $key => $value) {
723
$this->setOption($key, $value);
730
* adds a foreignKey to this table
734
public function addForeignKey(array $definition)
736
$this->_options['foreignKeys'][] = $definition;
742
* adds a check constraint to this table
746
public function addCheckConstraint($definition, $name)
748
if (is_string($name)) {
749
$this->_options['checks'][$name] = $definition;
751
$this->_options['checks'][] = $definition;
760
* adds an index to this table
764
public function addIndex($index, array $definition)
766
if (isset($definition['fields'])) {
767
foreach ((array) $definition['fields'] as $key => $field) {
768
if (is_numeric($key)) {
769
$definition['fields'][$key] = $this->getColumnName($field);
771
$columnName = $this->getColumnName($key);
773
unset($definition['fields'][$key]);
775
$definition['fields'][$columnName] = $field;
780
$this->_options['indexes'][$index] = $definition;
786
* @return array|boolean array on success, FALSE on failure
788
public function getIndex($index)
790
if (isset($this->_options['indexes'][$index])) {
791
return $this->_options['indexes'][$index];
798
* DESCRIBE WHAT THIS METHOD DOES, PLEASE!
800
* @todo Name proposal: addRelation
802
public function bind($args, $type)
804
$options = ( ! isset($args[1])) ? array() : $args[1];
805
$options['type'] = $type;
807
$this->_parser->bind($args[0], $options);
813
* @param string $alias the relation to check if exists
814
* @return boolean true if the relation exists otherwise false
816
public function hasRelation($alias)
818
return $this->_parser->hasRelation($alias);
824
* @param string $alias relation alias
826
public function getRelation($alias, $recursive = true)
828
return $this->_parser->getRelation($alias, $recursive);
833
* returns an array containing all relation objects
835
* @return array an array of Doctrine_Relation objects
837
public function getRelations()
839
return $this->_parser->getRelations();
844
* creates a new Doctrine_Query object and adds the component name
845
* of this table as the query 'from' part
847
* @param string Optional alias name for component aliasing.
849
* @return Doctrine_Query
851
public function createQuery($alias = '')
853
if ( ! empty($alias)) {
854
$alias = ' ' . trim($alias);
856
return Doctrine_Query::create($this->_conn)->from($this->getComponentName() . $alias);
862
* @return Doctrine_Table_Repository
864
public function getRepository()
866
return $this->_repository;
871
* sets an option and returns this object in order to
872
* allow flexible method chaining
874
* @see Doctrine_Table::$_options for available options
875
* @param string $name the name of the option to set
876
* @param mixed $value the value of the option
877
* @return Doctrine_Table this object
879
public function setOption($name, $value)
886
case 'inheritanceMap':
889
if ( ! is_array($value)) {
890
throw new Doctrine_Table_Exception($name . ' should be an array.');
894
$this->_options[$name] = $value;
899
* returns the value of given option
901
* @param string $name the name of the option
902
* @return mixed the value of given option
904
public function getOption($name)
906
if (isset($this->_options[$name])) {
907
return $this->_options[$name];
915
* returns a column name for column alias
916
* if the actual name for the alias cannot be found
917
* this method returns the given alias
919
* @param string $alias column alias
920
* @return string column name
922
public function getColumnName($fieldName)
924
// FIX ME: This is being used in places where an array is passed, but it should not be an array
925
// For example in places where Doctrine should support composite foreign/primary keys
926
$fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName;
928
if (isset($this->_columnNames[$fieldName])) {
929
return $this->_columnNames[$fieldName];
932
return strtolower($fieldName);
936
* getColumnDefinition
938
* @param string $columnName
941
public function getColumnDefinition($columnName)
943
if ( ! isset($this->_columns[$columnName])) {
946
return $this->_columns[$columnName];
952
* returns a column alias for a column name
953
* if no alias can be found the column name is returned.
955
* @param string $columnName column name
956
* @return string column alias
958
public function getFieldName($columnName)
960
if (isset($this->_fieldNames[$columnName])) {
961
return $this->_fieldNames[$columnName];
965
public function setColumns(array $definitions)
967
foreach ($definitions as $name => $options) {
968
$this->setColumn($name, $options['type'], $options['length'], $options);
974
* @param string $name
975
* @param string $type
976
* @param integer $length
977
* @param mixed $options
978
* @param boolean $prepend Whether to prepend or append the new column to the column list.
979
* By default the column gets appended.
980
* @throws Doctrine_Table_Exception if trying use wrongly typed parameter
983
public function setColumn($name, $type, $length = null, $options = array(), $prepend = false)
985
if (is_string($options)) {
986
$options = explode('|', $options);
989
foreach ($options as $k => $option) {
990
if (is_numeric($k)) {
991
if ( ! empty($option)) {
992
$options[$option] = true;
998
// extract column name & field name
999
if (stripos($name, ' as '))
1001
if (strpos($name, ' as')) {
1002
$parts = explode(' as ', $name);
1004
$parts = explode(' AS ', $name);
1007
if (count($parts) > 1) {
1008
$fieldName = $parts[1];
1010
$fieldName = $parts[0];
1013
$name = strtolower($parts[0]);
1016
$name = strtolower($name);
1019
$name = trim($name);
1020
$fieldName = trim($fieldName);
1023
$this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
1024
$this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
1026
$this->_columnNames[$fieldName] = $name;
1027
$this->_fieldNames[$name] = $fieldName;
1030
if ($length == null) {
1044
$length = 2147483647;
1049
// YYYY-MM-DD ISO 8601
1052
// HH:NN:SS+00:00 ISO 8601
1055
// YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
1061
$options['type'] = $type;
1062
$options['length'] = $length;
1065
$this->_columns = array_merge(array($name => $options), $this->_columns);
1067
$this->_columns[$name] = $options;
1070
if (isset($options['primary']) && $options['primary']) {
1071
if (isset($this->_identifier)) {
1072
$this->_identifier = (array) $this->_identifier;
1074
if ( ! in_array($fieldName, $this->_identifier)) {
1075
$this->_identifier[] = $fieldName;
1078
if (isset($options['default'])) {
1079
$this->hasDefaultValues = true;
1085
* returns true if this table has default values, otherwise false
1089
public function hasDefaultValues()
1091
return $this->hasDefaultValues;
1096
* returns the default value(if any) for given column
1098
* @param string $fieldName
1101
public function getDefaultValueOf($fieldName)
1103
$columnName = $this->getColumnName($fieldName);
1104
if ( ! isset($this->_columns[$columnName])) {
1105
throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
1107
if (isset($this->_columns[$columnName]['default'])) {
1108
return $this->_columns[$columnName]['default'];
1117
public function getIdentifier()
1119
return $this->_identifier;
1125
public function getIdentifierType()
1127
return $this->_identifierType;
1134
public function hasColumn($columnName)
1136
return isset($this->_columns[strtolower($columnName)]);
1143
public function hasField($fieldName)
1145
return isset($this->_columnNames[$fieldName]);
1149
* sets the connection for this class
1151
* @params Doctrine_Connection a connection object
1152
* @return Doctrine_Table this object
1154
public function setConnection(Doctrine_Connection $conn)
1156
$this->_conn = $conn;
1158
$this->setParent($this->_conn);
1164
* returns the connection associated with this table (if any)
1166
* @return Doctrine_Connection|null the connection object
1168
public function getConnection()
1170
return $this->_conn;
1174
* creates a new record
1176
* @param $array an array where keys are field names and
1177
* values representing field values
1178
* @return Doctrine_Record the created record object
1180
public function create(array $array = array())
1182
$record = new $this->_options['name']($this, true);
1183
$record->fromArray($array);
1189
* adds a named query in the query registry
1191
* @param $queryKey Query key name
1192
* @param $query DQL string or Doctrine_Query object
1195
public function addNamedQuery($queryKey, $query)
1197
$registry = Doctrine_Manager::getInstance()->getQueryRegistry();
1198
$registry->add($this->getComponentName() . '/' . $queryKey, $query);
1202
* creates a named query in the query registry
1204
* @param $queryKey Query key name
1205
* @return Doctrine_Query
1207
public function createNamedQuery($queryKey)
1209
$queryRegistry = Doctrine_Manager::getInstance()->getQueryRegistry();
1211
if (strpos($queryKey, '/') !== false) {
1212
$e = explode('/', $queryKey);
1214
return $queryRegistry->get($e[1], $e[0]);
1217
return $queryRegistry->get($queryKey, $this->getComponentName());
1222
* finds a record by its identifier
1224
* @param mixed $name Database Row ID or Query Name defined previously as a NamedQuery
1225
* @param mixed $params This argument is the hydration mode (Doctrine::HYDRATE_ARRAY or
1226
* Doctrine::HYDRATE_RECORD) if first param is a Database Row ID.
1227
* Otherwise this argument expect an array of query params.
1228
* @param int $hydrationMode Optional Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD if
1229
* first argument is a NamedQuery
1230
* @return mixed Doctrine_Collection, array, Doctrine_Record or false if no result
1232
public function find()
1234
$num_args = func_num_args();
1236
// Named Query or IDs
1237
$name = func_get_arg(0);
1239
if (is_null($name)) {
1243
$ns = $this->getComponentName();
1246
// Check for possible cross-access
1247
if ( ! is_array($name) && strpos($name, '/') !== false) {
1248
list($ns, $m) = explode('/', $name);
1251
// Define query to be used
1253
! is_array($name) &&
1254
Doctrine_Manager::getInstance()->getQueryRegistry()->has($m, $ns)
1256
// We're dealing with a named query
1257
$q = $this->createNamedQuery($name);
1259
// Parameters construction
1260
$params = ($num_args >= 2) ? func_get_arg(1) : array();
1263
$hydrationMode = ($num_args == 3) ? func_get_arg(2) : null;
1266
$res = $q->execute($params, $hydrationMode);
1268
// We're passing a single ID or an array of IDs
1269
$q = $this->createQuery('dctrn_find')
1270
->where('dctrn_find.' . implode(' = ? AND dctrn_find.', (array) $this->getIdentifier()) . ' = ?')
1273
// Parameters construction
1274
$params = is_array($name) ? array_values($name) : array($name);
1277
$hydrationMode = ($num_args == 2) ? func_get_arg(1) : null;
1280
$res = $q->fetchOne($params, $hydrationMode);
1290
* returns a collection of records
1292
* @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD
1293
* @return Doctrine_Collection
1295
public function findAll($hydrationMode = null)
1297
return $this->createQuery('dctrn_find')
1298
->execute(array(), $hydrationMode);
1303
* finds records with given SQL where clause
1304
* returns a collection of records
1306
* @param string $dql DQL after WHERE clause
1307
* @param array $params query parameters
1308
* @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD
1309
* @return Doctrine_Collection
1311
* @todo This actually takes DQL, not SQL, but it requires column names
1312
* instead of field names. This should be fixed to use raw SQL instead.
1314
public function findBySql($dql, $params = array(), $hydrationMode = null)
1316
return $this->createQuery('dctrn_find')
1317
->where($dql)->execute($params, $hydrationMode);
1322
* finds records with given DQL where clause
1323
* returns a collection of records
1325
* @param string $dql DQL after WHERE clause
1326
* @param array $params query parameters
1327
* @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD
1328
* @return Doctrine_Collection
1330
public function findByDql($dql, $params = array(), $hydrationMode = null)
1332
$parser = new Doctrine_Query($this->_conn);
1333
$component = $this->getComponentName();
1334
$query = 'FROM ' . $component . ' dctrn_find WHERE ' . $dql;
1336
return $parser->query($query, $params, $hydrationMode);
1342
* @param string $column
1343
* @param string $value
1344
* @param string $hydrationMode
1347
protected function findBy($fieldName, $value, $hydrationMode = null)
1349
return $this->createQuery('dctrn_find')
1350
->where('dctrn_find.' . $fieldName . ' = ?', array($value))
1351
->execute(array(), $hydrationMode);
1357
* @param string $column
1358
* @param string $value
1359
* @param string $hydrationMode
1362
protected function findOneBy($fieldName, $value, $hydrationMode = null)
1364
return $this->createQuery('dctrn_find')
1365
->where('dctrn_find.' . $fieldName . ' = ?', array($value))
1367
->fetchOne(array(), $hydrationMode);
1372
* fetches data using the provided queryKey and
1373
* the associated query in the query registry
1375
* if no query for given queryKey is being found a
1376
* Doctrine_Query_Registry exception is being thrown
1378
* @param string $queryKey the query key
1379
* @param array $params prepared statement params (if any)
1380
* @return mixed the fetched data
1382
public function execute($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
1384
return $this->createNamedQuery($queryKey)->execute($params, $hydrationMode);
1389
* fetches data using the provided queryKey and
1390
* the associated query in the query registry
1392
* if no query for given queryKey is being found a
1393
* Doctrine_Query_Registry exception is being thrown
1395
* @param string $queryKey the query key
1396
* @param array $params prepared statement params (if any)
1397
* @return mixed the fetched data
1399
public function executeOne($queryKey, $params = array(), $hydrationMode = Doctrine::HYDRATE_RECORD)
1401
return $this->createNamedQuery($queryKey)->fetchOne($params, $hydrationMode);
1406
* clears the first level cache (identityMap)
1409
* @todo what about a more descriptive name? clearIdentityMap?
1411
public function clear()
1413
$this->_identityMap = array();
1418
* adds a record to identity map
1420
* @param Doctrine_Record $record record to be added
1422
* @todo Better name? registerRecord?
1424
public function addRecord(Doctrine_Record $record)
1426
$id = implode(' ', $record->identifier());
1428
if (isset($this->_identityMap[$id])) {
1432
$this->_identityMap[$id] = $record;
1439
* removes a record from the identity map, returning true if the record
1440
* was found and removed and false if the record wasn't found.
1442
* @param Doctrine_Record $record record to be removed
1445
public function removeRecord(Doctrine_Record $record)
1447
$id = implode(' ', $record->identifier());
1449
if (isset($this->_identityMap[$id])) {
1450
unset($this->_identityMap[$id]);
1459
* first checks if record exists in identityMap, if not
1460
* returns a new record
1462
* @return Doctrine_Record
1464
public function getRecord()
1466
if ( ! empty($this->_data)) {
1467
$identifierFieldNames = $this->getIdentifier();
1469
if ( ! is_array($identifierFieldNames)) {
1470
$identifierFieldNames = array($identifierFieldNames);
1474
foreach ($identifierFieldNames as $fieldName) {
1475
if ( ! isset($this->_data[$fieldName])) {
1476
// primary key column not found return new record
1480
$id[] = $this->_data[$fieldName];
1484
$recordName = $this->getComponentName();
1485
$record = new $recordName($this, true);
1486
$this->_data = array();
1490
$id = implode(' ', $id);
1492
if (isset($this->_identityMap[$id])) {
1493
$record = $this->_identityMap[$id];
1494
if ($record->getTable()->getAttribute(Doctrine::ATTR_HYDRATE_OVERWRITE)) {
1495
$record->hydrate($this->_data);
1496
if ($record->state() == Doctrine_Record::STATE_PROXY) {
1497
if (count($this->_data) >= $this->getColumnCount()) {
1498
$record->state(Doctrine_Record::STATE_CLEAN);
1502
$record->hydrate($this->_data, false);
1505
$recordName = $this->getComponentName();
1506
$record = new $recordName($this);
1507
$this->_identityMap[$id] = $record;
1509
$this->_data = array();
1511
$recordName = $this->getComponentName();
1512
$record = new $recordName($this, true);
1519
* Get the classname to return. Most often this is just the options['name']
1521
* Check the subclasses option and the inheritanceMap for each subclass to see
1522
* if all the maps in a subclass is met. If this is the case return that
1523
* subclass name. If no subclasses match or if there are no subclasses defined
1524
* return the name of the class for this tables record.
1526
* @todo this function could use reflection to check the first time it runs
1527
* if the subclassing option is not set.
1529
* @return string The name of the class to create
1532
public function getClassnameToReturn()
1534
if ( ! isset($this->_options['subclasses'])) {
1535
return $this->_options['name'];
1537
foreach ($this->_options['subclasses'] as $subclass) {
1538
$table = $this->_conn->getTable($subclass);
1539
$inheritanceMap = $table->getOption('inheritanceMap');
1541
foreach ($inheritanceMap as $key => $value) {
1542
if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) {
1548
return $table->getComponentName();
1551
return $this->_options['name'];
1555
* @param $id database row id
1556
* @throws Doctrine_Find_Exception
1558
final public function getProxy($id = null)
1561
$identifierColumnNames = $this->getIdentifierColumnNames();
1562
$query = 'SELECT ' . implode(', ', (array) $identifierColumnNames)
1563
. ' FROM ' . $this->getTableName()
1564
. ' WHERE ' . implode(' = ? && ', (array) $identifierColumnNames) . ' = ?';
1565
$query = $this->applyInheritance($query);
1567
$params = array_merge(array($id), array_values($this->_options['inheritanceMap']));
1569
$this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
1571
if ($this->_data === false)
1574
return $this->getRecord();
1579
* @param $where query where part to be modified
1580
* @return string query where part with column aggregation inheritance added
1582
final public function applyInheritance($where)
1584
if ( ! empty($this->_options['inheritanceMap'])) {
1586
foreach ($this->_options['inheritanceMap'] as $field => $value) {
1587
$a[] = $this->getColumnName($field) . ' = ?';
1589
$i = implode(' AND ', $a);
1590
$where .= ' AND ' . $i;
1600
public function count()
1602
return $this->createQuery()->count();
1606
* @return Doctrine_Query a Doctrine_Query object
1608
public function getQueryObject()
1610
$graph = new Doctrine_Query($this->getConnection());
1611
$graph->load($this->getComponentName());
1616
* @param string $fieldName
1619
public function getEnumValues($fieldName)
1621
$columnName = $this->getColumnName($fieldName);
1622
if (isset($this->_columns[$columnName]['values'])) {
1623
return $this->_columns[$columnName]['values'];
1632
* @param string $field
1633
* @param integer $index
1636
public function enumValue($fieldName, $index)
1638
if ($index instanceof Doctrine_Null) {
1642
$columnName = $this->getColumnName($fieldName);
1643
if ( ! $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)
1644
&& isset($this->_columns[$columnName]['values'][$index])
1646
return $this->_columns[$columnName]['values'][$index];
1655
* @param string $field
1656
* @param mixed $value
1659
public function enumIndex($fieldName, $value)
1661
$values = $this->getEnumValues($fieldName);
1663
$index = array_search($value, $values);
1664
if ($index === false || !$this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) {
1673
* @param string $name
1674
* @param string $value
1675
* @param Doctrine_Record $record
1676
* @return Doctrine_Validator_ErrorStack $errorStack
1678
public function validateField($fieldName, $value, Doctrine_Record $record = null)
1680
if ($record instanceof Doctrine_Record) {
1681
$errorStack = $record->getErrorStack();
1683
$record = $this->create();
1684
$errorStack = new Doctrine_Validator_ErrorStack($this->getOption('name'));
1687
if ($value === self::$_null) {
1689
} else if ($value instanceof Doctrine_Record && $value->exists()) {
1690
$value = $value->getIncremented();
1691
} else if ($value instanceof Doctrine_Record && ! $value->exists()) {
1692
foreach($this->getRelations() as $relation) {
1693
if ($fieldName == $relation->getLocalFieldName() && (get_class($value) == $relation->getClass() || is_subclass_of($value, $relation->getClass()))) {
1699
$dataType = $this->getTypeOf($fieldName);
1701
// Validate field type, if type validation is enabled
1702
if ($this->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_TYPES) {
1703
if ( ! Doctrine_Validator::isValidType($value, $dataType)) {
1704
$errorStack->add($fieldName, 'type');
1706
if ($dataType == 'enum') {
1707
$enumIndex = $this->enumIndex($fieldName, $value);
1708
if ($enumIndex === false && $value !== null) {
1709
$errorStack->add($fieldName, 'enum');
1714
// Validate field length, if length validation is enabled
1715
if ($this->getAttribute(Doctrine::ATTR_VALIDATE) & Doctrine::VALIDATE_LENGTHS) {
1716
if ( ! Doctrine_Validator::validateLength($value, $dataType, $this->getFieldLength($fieldName))) {
1717
$errorStack->add($fieldName, 'length');
1721
// Run all custom validators
1722
foreach ($this->getFieldValidators($fieldName) as $validatorName => $args) {
1723
if ( ! is_string($validatorName)) {
1724
$validatorName = $args;
1728
$validator = Doctrine_Validator::getValidator($validatorName);
1729
$validator->invoker = $record;
1730
$validator->field = $fieldName;
1731
$validator->args = $args;
1732
if ( ! $validator->validate($value)) {
1733
$errorStack->add($fieldName, $validator);
1743
* @return integer the number of columns in this table
1745
public function getColumnCount()
1747
return $this->columnCount;
1751
* returns all columns and their definitions
1755
public function getColumns()
1757
return $this->_columns;
1761
* Removes the passed field name from the table schema information
1765
public function removeColumn($fieldName)
1767
if ( ! $this->hasField($fieldName)) {
1771
$columnName = $this->getColumnName($fieldName);
1772
unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]);
1773
$this->columnCount = count($this->_columns);
1778
* returns an array containing all the column names.
1782
public function getColumnNames(array $fieldNames = null)
1784
if ($fieldNames === null) {
1785
return array_keys($this->_columns);
1787
$columnNames = array();
1788
foreach ($fieldNames as $fieldName) {
1789
$columnNames[] = $this->getColumnName($fieldName);
1791
return $columnNames;
1796
* returns an array with all the identifier column names.
1800
public function getIdentifierColumnNames()
1802
return $this->getColumnNames((array) $this->getIdentifier());
1806
* returns an array containing all the field names.
1810
public function getFieldNames()
1812
return array_values($this->_fieldNames);
1818
* @return mixed array on success, false on failure
1820
public function getDefinitionOf($fieldName)
1822
$columnName = $this->getColumnName($fieldName);
1823
return $this->getColumnDefinition($columnName);
1829
* @return mixed string on success, false on failure
1831
public function getTypeOf($fieldName)
1833
return $this->getTypeOfColumn($this->getColumnName($fieldName));
1839
* @return mixed The column type or FALSE if the type cant be determined.
1841
public function getTypeOfColumn($columnName)
1843
return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
1848
* doctrine uses this function internally
1849
* users are strongly discouraged to use this function
1851
* @param array $data internal data
1854
public function setData(array $data)
1856
$this->_data = $data;
1860
* returns internal data, used by Doctrine_Record instances
1861
* when retrieving data from database
1865
public function getData()
1867
return $this->_data;
1872
* this method performs special data preparation depending on
1873
* the type of the given column
1875
* 1. It unserializes array and object typed columns
1876
* 2. Uncompresses gzip typed columns
1877
* 3. Gets the appropriate enum values for enum typed columns
1878
* 4. Initializes special null object pointer for null values (for fast column existence checking purposes)
1884
* $table->prepareValue($field, $value); // Doctrine_Null
1887
* @throws Doctrine_Table_Exception if unserialization of array/object typed column fails or
1888
* @throws Doctrine_Table_Exception if uncompression of gzip typed column fails *
1889
* @param string $field the name of the field
1890
* @param string $value field value
1891
* @param string $typeHint Type hint used to pass in the type of the value to prepare
1892
* if it is already known. This enables the method to skip
1893
* the type determination. Used i.e. during hydration.
1894
* @return mixed prepared value
1896
public function prepareValue($fieldName, $value, $typeHint = null)
1898
if ($value === self::$_null) {
1899
return self::$_null;
1900
} else if ($value === null) {
1903
$type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint;
1908
// don't do any casting here PHP INT_MAX is smaller than what the databases support
1911
return $this->enumValue($fieldName, $value);
1914
return (boolean) $value;
1918
if (is_string($value)) {
1919
$value = empty($value) ? null:unserialize($value);
1921
if ($value === false) {
1922
throw new Doctrine_Table_Exception('Unserialization of ' . $fieldName . ' failed.');
1928
$value = gzuncompress($value);
1930
if ($value === false) {
1931
throw new Doctrine_Table_Exception('Uncompressing of ' . $fieldName . ' failed.');
1943
* getter for associated tree
1945
* @return mixed if tree return instance of Doctrine_Tree, otherwise returns false
1947
public function getTree()
1949
if (isset($this->_options['treeImpl'])) {
1950
if ( ! $this->_tree) {
1951
$options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array();
1952
$this->_tree = Doctrine_Tree::factory($this,
1953
$this->_options['treeImpl'],
1957
return $this->_tree;
1967
public function getComponentName()
1969
return $this->_options['name'];
1977
public function getTableName()
1979
return $this->_options['tableName'];
1985
* @param string $tableName
1988
public function setTableName($tableName)
1990
$this->setOption('tableName', $this->_conn->formatter->getTableName($tableName));
1996
* determine if table acts as tree
1998
* @return mixed if tree return true, otherwise returns false
2000
public function isTree()
2002
return ( ! is_null($this->_options['treeImpl'])) ? true : false;
2007
* returns all templates attached to this table
2009
* @return array an array containing all templates
2011
public function getTemplates()
2013
return $this->_templates;
2019
* @param string $template
2022
public function getTemplate($template)
2024
if ( ! isset($this->_templates[$template])) {
2025
throw new Doctrine_Table_Exception('Template ' . $template . ' not loaded');
2028
return $this->_templates[$template];
2032
* Check if the table has a template name
2034
* @param string $template
2035
* @return boolean $bool
2037
public function hasTemplate($template)
2039
return isset($this->_templates[$template]);
2043
* Add template to the table
2045
* @param string $template
2046
* @param Doctrine_Template $impl
2047
* @return Doctrine_Table
2049
public function addTemplate($template, Doctrine_Template $impl)
2051
$this->_templates[$template] = $impl;
2057
* Get all the generators for the table
2059
* @return array $generators
2062
public function getGenerators()
2064
return $this->_generators;
2068
* Get generator instance for a passed name
2070
* @param string $generator
2071
* @return Doctrine_Record_Generator $generator
2073
public function getGenerator($generator)
2075
if ( ! isset($this->_generators[$generator])) {
2076
throw new Doctrine_Table_Exception('Generator ' . $generator . ' not loaded');
2079
return $this->_generators[$generator];
2083
* Check if a generator name exists
2085
* @param string $generator
2088
public function hasGenerator($generator)
2090
return isset($this->_generators[$generator]);
2094
* Add a generate to the table instance
2096
* @param Doctrine_Record_Generator $generator
2097
* @param string $name
2098
* @return Doctrine_Table
2100
public function addGenerator(Doctrine_Record_Generator $generator, $name = null)
2102
if ($name === null) {
2103
$this->_generators[] = $generator;
2105
$this->_generators[$name] = $generator;
2112
* binds query parts to given component
2114
* @param array $queryParts an array of pre-bound query parts
2115
* @return Doctrine_Record this object
2117
public function bindQueryParts(array $queryParts)
2119
$this->_options['queryParts'] = $queryParts;
2126
* binds given value to given query part
2128
* @param string $queryPart
2129
* @param mixed $value
2130
* @return Doctrine_Record this object
2132
public function bindQueryPart($queryPart, $value)
2134
$this->_options['queryParts'][$queryPart] = $value;
2140
* Gets the names of all validators that are applied on a field.
2142
* @param string The field name.
2143
* @return array The names of all validators that are applied on the specified field.
2145
public function getFieldValidators($fieldName)
2147
$validators = array();
2148
$columnName = $this->getColumnName($fieldName);
2149
// this loop is a dirty workaround to get the validators filtered out of
2150
// the options, since everything is squeezed together currently
2151
foreach ($this->_columns[$columnName] as $name => $args) {
2153
|| $name == 'primary'
2154
|| $name == 'protected'
2155
|| $name == 'autoincrement'
2156
|| $name == 'default'
2157
|| $name == 'values'
2158
|| $name == 'sequence'
2159
|| $name == 'zerofill'
2163
|| $name == 'length'
2165
|| $name == 'comment') {
2168
if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])
2169
&& $this->_columns[$columnName]['autoincrement'] === true) {
2172
// skip it if it's explicitly set to FALSE (i.e. notnull => false)
2173
if ($args === false) {
2176
$validators[$name] = $args;
2183
* Gets the (maximum) length of a field.
2185
public function getFieldLength($fieldName)
2187
return $this->_columns[$this->getColumnName($fieldName)]['length'];
2193
* @param string $queryPart
2194
* @return string $queryPart
2196
public function getBoundQueryPart($queryPart)
2198
if ( ! isset($this->_options['queryParts'][$queryPart])) {
2202
return $this->_options['queryParts'][$queryPart];
2208
* @param object Doctrine_Record_Filter $filter
2209
* @return object $this
2211
public function unshiftFilter(Doctrine_Record_Filter $filter)
2213
$filter->setTable($this);
2217
array_unshift($this->_filters, $filter);
2225
* @return array $filters
2227
public function getFilters()
2229
return $this->_filters;
2233
* returns a string representation of this object
2237
public function __toString()
2239
return Doctrine_Lib::getTableAsString($this);
2243
* Resolve the passed find by field name to the appropriate field name
2244
* regardless of whether the user passes a column name, field name, or a Doctrine_Inflector::classified()
2245
* version of their column name. It will be inflected with Doctrine_Inflector::tableize()
2246
* to get the column or field name
2248
* @param string $name
2249
* @return string $fieldName
2251
protected function _resolveFindByFieldName($name)
2253
$fieldName = Doctrine_Inflector::tableize($name);
2254
if ($this->hasColumn($name) || $this->hasField($name)) {
2255
return $this->getFieldName($this->getColumnName($name));
2256
} else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) {
2257
return $this->getFieldName($this->getColumnName($fieldName));
2266
* Adds support for magic finders.
2267
* findByColumnName, findByRelationAlias
2268
* findById, findByContactId, etc.
2272
public function __call($method, $arguments)
2274
$lcMethod = strtolower($method);
2276
if (substr($lcMethod, 0, 6) == 'findby') {
2277
$by = substr($method, 6, strlen($method));
2279
} else if (substr($lcMethod, 0, 9) == 'findoneby') {
2280
$by = substr($method, 9, strlen($method));
2281
$method = 'findOneBy';
2285
if ( ! isset($arguments[0])) {
2286
throw new Doctrine_Table_Exception('You must specify the value to findBy');
2289
$fieldName = $this->_resolveFindByFieldName($by);
2290
$hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
2291
if ($this->hasField($fieldName)) {
2292
return $this->$method($fieldName, $arguments[0], $hydrationMode);
2293
} else if ($this->hasRelation($by)) {
2294
$relation = $this->getRelation($by);
2296
if ($relation['type'] === Doctrine_Relation::MANY) {
2297
throw new Doctrine_Table_Exception('Cannot findBy many relationship.');
2300
return $this->$method($relation['local'], $arguments[0], $hydrationMode);
2302
throw new Doctrine_Table_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.');
2306
// Forward the method on to the record instance and see if it has anything or one of its behaviors
2308
return call_user_func_array(array($this->getRecordInstance(), $method . 'TableProxy'), $arguments);
2309
} catch (Doctrine_Record_UnknownPropertyException $e) {}
2311
throw new Doctrine_Table_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));