2814 lines
92 KiB
PHP
2814 lines
92 KiB
PHP
<?php
|
|
/*
|
|
* $Id: Table.php 7490 2010-03-29 19:53:27Z jwage $
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* This software consists of voluntary contributions made by many individuals
|
|
* and is licensed under the LGPL. For more information, see
|
|
* <http://www.doctrine-project.org>.
|
|
*/
|
|
|
|
/**
|
|
* Doctrine_Table represents a database table
|
|
* each Doctrine_Table holds the information of foreignKeys and associations
|
|
*
|
|
*
|
|
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
|
* @package Doctrine
|
|
* @subpackage Table
|
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
* @version $Revision: 7490 $
|
|
* @link www.doctrine-project.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(),
|
|
'orderBy' => null
|
|
);
|
|
|
|
/**
|
|
* @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();
|
|
|
|
/**
|
|
* Generator instance responsible for constructing this table
|
|
*
|
|
* @see Doctrine_Record_Generator
|
|
* @var Doctrine_Record_Generator $generator
|
|
*/
|
|
protected $_generator;
|
|
|
|
/**
|
|
* @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 ($charset = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_TABLE_CHARSET)) {
|
|
$this->_options['charset'] = $charset;
|
|
}
|
|
if ($collate = $this->getAttribute(Doctrine_Core::ATTR_DEFAULT_TABLE_COLLATE)) {
|
|
$this->_options['collate'] = $collate;
|
|
}
|
|
|
|
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);
|
|
|
|
$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_Core::IDENTIFIER_AUTOINC)
|
|
? $table->getIdentifierType() : Doctrine_Core::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_Core::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_Core::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_Core::IDENTIFIER_AUTOINC;
|
|
$found = true;
|
|
}
|
|
break;
|
|
case 'seq':
|
|
case 'sequence':
|
|
$this->_identifierType = Doctrine_Core::IDENTIFIER_SEQUENCE;
|
|
$found = true;
|
|
|
|
if (is_string($value)) {
|
|
$this->_options['sequenceName'] = $value;
|
|
} else {
|
|
if (($sequence = $this->getAttribute(Doctrine_Core::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_Core::IDENTIFIER_NATURAL;
|
|
}
|
|
}
|
|
|
|
$this->_identifier = $pk;
|
|
|
|
break;
|
|
default:
|
|
$this->_identifierType = Doctrine_Core::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_Core::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_Core::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_Core::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_Core::ATTR_EXPORT) & Doctrine_Core::EXPORT_CONSTRAINTS) {
|
|
|
|
$constraints = array();
|
|
|
|
$emptyIntegrity = array('onUpdate' => null,
|
|
'onDelete' => null);
|
|
|
|
foreach ($this->getRelations() as $name => $relation) {
|
|
$fk = $relation->toArray();
|
|
$fk['foreignTable'] = $relation->getTable()->getTableName();
|
|
|
|
// do not touch tables that have EXPORT_NONE attribute
|
|
if ($relation->getTable()->getAttribute(Doctrine_Core::ATTR_EXPORT) === Doctrine_Core::EXPORT_NONE) {
|
|
continue;
|
|
}
|
|
|
|
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.
|
|
* <code>
|
|
* foreach ($table->indexes as $name => $definition) {
|
|
* // ...
|
|
* }
|
|
* </code>
|
|
*
|
|
* @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.
|
|
* <code>
|
|
* if (isset($table->tableName)) {
|
|
* // ...
|
|
* }
|
|
* </code>
|
|
*
|
|
* @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
|
|
* @param array $options array of options for unique validator
|
|
* @param bool $createUniqueIndex Whether or not to create a unique index in the database
|
|
* @return void
|
|
*/
|
|
public function unique($fields, $options = array(), $createdUniqueIndex = true)
|
|
{
|
|
if ($createdUniqueIndex) {
|
|
$name = implode('_', $fields) . '_unqidx';
|
|
$definition = array('type' => 'unique', 'fields' => $fields);
|
|
$this->addIndex($name, $definition);
|
|
}
|
|
|
|
$this->_uniques[] = array($fields, $options);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Binds One-to-One aggregate relation
|
|
*
|
|
* @param string $componentName the name of the related component
|
|
* @param string $options relation options
|
|
* @see Doctrine_Relation::_$definition
|
|
* @return Doctrine_Record this object
|
|
*/
|
|
public function hasOne()
|
|
{
|
|
$this->bind(func_get_args(), Doctrine_Relation::ONE);
|
|
}
|
|
|
|
/**
|
|
* Binds One-to-Many / Many-to-Many aggregate relation
|
|
*
|
|
* @param string $componentName the name of the related component
|
|
* @param string $options relation options
|
|
* @see Doctrine_Relation::_$definition
|
|
* @return Doctrine_Record this object
|
|
*/
|
|
public function hasMany()
|
|
{
|
|
$this->bind(func_get_args(), Doctrine_Relation::MANY);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* <code>
|
|
* $table = Doctrine_Core::getTable('User');
|
|
* $table->createQuery('myuser')
|
|
* ->where('myuser.Phonenumber = ?', '5551234');
|
|
* </code>
|
|
*
|
|
* @param string $alias name for component aliasing
|
|
* @return Doctrine_Query
|
|
*/
|
|
public function createQuery($alias = '')
|
|
{
|
|
if ( ! empty($alias)) {
|
|
$alias = ' ' . trim($alias);
|
|
}
|
|
|
|
$class = $this->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS);
|
|
|
|
return Doctrine_Query::create(null, $class)
|
|
->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;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the table orderby statement
|
|
*
|
|
* @param string $alias The alias to use
|
|
* @param boolean $columnNames Whether or not to use column names instead of field names
|
|
* @return string $orderByStatement
|
|
*/
|
|
public function getOrderByStatement($alias = null, $columnNames = false)
|
|
{
|
|
if (isset($this->_options['orderBy'])) {
|
|
return $this->processOrderBy($alias, $this->_options['orderBy']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process an order by statement to be prefixed with the passed alias and
|
|
* field names converted to column names if the 3rd argument is true.
|
|
*
|
|
* @param string $alias The alias to prefix columns with
|
|
* @param string $orderBy The order by to process
|
|
* @param string $columnNames Whether or not to convert field names to column names
|
|
* @return string $orderBy
|
|
*/
|
|
public function processOrderBy($alias, $orderBy, $columnNames = false)
|
|
{
|
|
if ( ! $alias) {
|
|
$alias = $this->getComponentName();
|
|
}
|
|
|
|
$e1 = explode(',', $orderBy);
|
|
$e1 = array_map('trim', $e1);
|
|
foreach ($e1 as $k => $v) {
|
|
$e2 = explode(' ', $v);
|
|
if ($columnNames) {
|
|
$e2[0] = $this->getColumnName($e2[0]);
|
|
}
|
|
if ($this->hasField($this->getFieldName($e2[0]))) {
|
|
$e1[$k] = $alias . '.' . $e2[0];
|
|
} else {
|
|
$e1[$k] = $e2[0];
|
|
}
|
|
if (isset($e2[1])) {
|
|
$e1[$k] .= ' ' . $e2[1];
|
|
}
|
|
}
|
|
|
|
return implode(', ', $e1);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Customize the array of options for a column or multiple columns. First
|
|
* argument can be a single field/column name or an array of them. The second
|
|
* argument is an array of options.
|
|
*
|
|
* [php]
|
|
* public function setTableDefinition()
|
|
* {
|
|
* parent::setTableDefinition();
|
|
* $this->setColumnOptions('username', array(
|
|
* 'unique' => true
|
|
* ));
|
|
* }
|
|
*
|
|
* @param string $columnName
|
|
* @param array $validators
|
|
* @return void
|
|
*/
|
|
public function setColumnOptions($columnName, array $options)
|
|
{
|
|
if (is_array($columnName)) {
|
|
foreach ($columnName as $name) {
|
|
$this->setColumnOptions($name, $options);
|
|
}
|
|
} else {
|
|
foreach ($options as $option => $value) {
|
|
$this->setColumnOption($columnName, $option, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set an individual column option
|
|
*
|
|
* @param string $columnName
|
|
* @param string $option
|
|
* @param string $value
|
|
* @return void
|
|
*/
|
|
public function setColumnOption($columnName, $option, $value)
|
|
{
|
|
if ($option == 'primary') {
|
|
if (isset($this->_identifier)) {
|
|
$this->_identifier = (array) $this->_identifier;
|
|
}
|
|
|
|
if ($value && ! in_array($columnName, $this->_identifier)) {
|
|
$this->_identifier[] = $columnName;
|
|
} else if (!$value && in_array($columnName, $this->_identifier)) {
|
|
$key = array_search($columnName, $this->_identifier);
|
|
unset($this->_identifier[$key]);
|
|
}
|
|
}
|
|
|
|
$columnName = $this->getColumnName($columnName);
|
|
$this->_columns[$columnName][$option] = $value;
|
|
}
|
|
|
|
/**
|
|
* Set multiple column definitions at once
|
|
*
|
|
* @param array $definitions
|
|
* @return void
|
|
*/
|
|
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_Core::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.
|
|
*
|
|
* <code>
|
|
* $table->find(11);
|
|
* $table->find(11, Doctrine_Core::HYDRATE_RECORD);
|
|
* $table->find('namedQueryForYearArchive', array(2009), Doctrine_Core::HYDRATE_ARRAY);
|
|
* </code>
|
|
*
|
|
* @param mixed $name Database Row ID or Query Name defined previously as a NamedQuery
|
|
* @param mixed $params This argument is the hydration mode (Doctrine_Core::HYDRATE_ARRAY or
|
|
* Doctrine_Core::HYDRATE_RECORD) if first param is a Database Row ID.
|
|
* Otherwise this argument expect an array of query params.
|
|
* @param int $hydrationMode Optional Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::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_Core::HYDRATE_ARRAY or Doctrine_Core::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_Core::HYDRATE_ARRAY or Doctrine_Core::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_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
|
|
* @return Doctrine_Collection|array
|
|
*/
|
|
public function findByDql($dql, $params = array(), $hydrationMode = null)
|
|
{
|
|
$parser = $this->createQuery();
|
|
$query = 'FROM ' . $this->getComponentName() . ' 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_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
|
|
* @return Doctrine_Collection|array
|
|
*/
|
|
public function findBy($fieldName, $value, $hydrationMode = null)
|
|
{
|
|
return $this->createQuery('dctrn_find')
|
|
->where($this->buildFindByWhere($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_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
|
|
* @return Doctrine_Record
|
|
*/
|
|
public function findOneBy($fieldName, $value, $hydrationMode = null)
|
|
{
|
|
return $this->createQuery('dctrn_find')
|
|
->where($this->buildFindByWhere($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_Core::HYDRATE_ARRAY or Doctrine_Core::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_Core::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_Core::HYDRATE_ARRAY or Doctrine_Core::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_Core::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_Core::ATTR_HYDRATE_OVERWRITE)) {
|
|
$record->hydrate($this->_data);
|
|
if ($record->state() == Doctrine_Record::STATE_PROXY) {
|
|
if (!$record->isInProxyState()) {
|
|
$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 = $this->createQuery();
|
|
$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_Core::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_Core::ATTR_USE_NATIVE_ENUM)) {
|
|
return $index;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Validates a given field using table ATTR_VALIDATE rules.
|
|
* @see Doctrine_Core::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_Core::ATTR_VALIDATE) & Doctrine_Core::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');
|
|
}
|
|
}
|
|
if ($dataType == 'set') {
|
|
$values = $this->_columns[$fieldName]['values'];
|
|
// Convert string to array
|
|
if (is_string($value)) {
|
|
$value = explode(',', $value);
|
|
$value = array_map('trim', $value);
|
|
$record->set($fieldName, $value);
|
|
}
|
|
// Make sure each set value is valid
|
|
foreach ($value as $k => $v) {
|
|
if ( ! in_array($v, $values)) {
|
|
$errorStack->add($fieldName, 'set');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate field length, if length validation is enabled
|
|
if ($this->getAttribute(Doctrine_Core::ATTR_VALIDATE) & Doctrine_Core::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 $unique)
|
|
{
|
|
list($fields, $options) = $unique;
|
|
$validator->args = $options;
|
|
$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:
|
|
* <code type='php'>
|
|
* $field = 'name';
|
|
* $value = null;
|
|
* $table->prepareValue($field, $value); // Doctrine_Null
|
|
* </code>
|
|
*
|
|
* @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 'set':
|
|
return explode(',', $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;
|
|
}
|
|
|
|
/**
|
|
* Set the generator responsible for creating this table
|
|
*
|
|
* @param Doctrine_Record_Generator $generator
|
|
* @return void
|
|
*/
|
|
public function setGenerator(Doctrine_Record_Generator $generator)
|
|
{
|
|
$this->_generator = $generator;
|
|
}
|
|
|
|
/**
|
|
* Check whether this table was created by a record generator or not
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isGenerator()
|
|
{
|
|
return isset($this->_generator) ? true : false;
|
|
}
|
|
|
|
/**
|
|
* Get the parent generator responsible for this table instance
|
|
*
|
|
* @return Doctrine_Record_Generator
|
|
*/
|
|
public function getParentGenerator()
|
|
{
|
|
return $this->_generator;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
public function buildFindByWhere($fieldName)
|
|
{
|
|
$ands = array();
|
|
$e = explode('And', $fieldName);
|
|
foreach ($e as $k => $v) {
|
|
$and = '';
|
|
$e2 = explode('Or', $v);
|
|
$ors = array();
|
|
foreach ($e2 as $k2 => $v2) {
|
|
if ($v2 = $this->_resolveFindByFieldName($v2)) {
|
|
$ors[] = 'dctrn_find.' . $v2 . ' = ?';
|
|
} else {
|
|
throw new Doctrine_Table_Exception('Invalid field name to find by: ' . $v2);
|
|
}
|
|
}
|
|
$and .= implode(' OR ', $ors);
|
|
$and = count($ors) > 1 ? '(' . $and . ')':$and;
|
|
$ands[] = $and;
|
|
}
|
|
$where = implode(' AND ', $ands);
|
|
return $where;
|
|
}
|
|
|
|
/**
|
|
* 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 ' . $method);
|
|
}
|
|
|
|
$fieldName = $this->_resolveFindByFieldName($by);
|
|
$count = count(explode('Or', $by)) + (count(explode('And', $by)) - 1);
|
|
if (count($arguments) > $count)
|
|
{
|
|
$hydrationMode = end($arguments);
|
|
unset($arguments[count($arguments) - 1]);
|
|
} else {
|
|
$hydrationMode = 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 {
|
|
return $this->$method($by, $arguments, $hydrationMode);
|
|
}
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
}
|