1474 lines
48 KiB
PHP
1474 lines
48 KiB
PHP
<?php
|
|
/*
|
|
* $Id: Table.php 1288 2007-04-20 23:58:28Z zYne $
|
|
*
|
|
* 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.phpdoctrine.com>.
|
|
*/
|
|
/**
|
|
* Doctrine_Table
|
|
*
|
|
* This class represents a database table.
|
|
* Each Doctrine_Table holds the information of foreignKeys and associations
|
|
* to other tables.
|
|
*
|
|
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
|
* @package Doctrine
|
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
* @version $Revision: 1288 $
|
|
* @category Object Relational Mapping
|
|
* @link www.phpdoctrine.com
|
|
* @since 1.0
|
|
*/
|
|
class Doctrine_Table extends Doctrine_Configurable implements Countable
|
|
{
|
|
/**
|
|
* @var array $data temporary data which is then loaded into Doctrine_Record::$data
|
|
*/
|
|
private $data = array();
|
|
/**
|
|
* @var array $relations an array containing all the Doctrine_Relation objects for this table
|
|
*/
|
|
private $relations = array();
|
|
/**
|
|
* @var array $primaryKeys an array containing all primary key column names
|
|
*/
|
|
private $primaryKeys = array();
|
|
/**
|
|
* @var mixed $identifier
|
|
*/
|
|
private $identifier;
|
|
/**
|
|
* @see Doctrine_Identifier constants
|
|
* @var integer $identifierType the type of identifier this table uses
|
|
*/
|
|
private $identifierType;
|
|
/**
|
|
* @var Doctrine_Connection $conn Doctrine_Connection object that created this table
|
|
*/
|
|
private $conn;
|
|
/**
|
|
* @var string $name
|
|
*/
|
|
private $name;
|
|
/**
|
|
* @var array $identityMap first level cache
|
|
*/
|
|
private $identityMap = array();
|
|
/**
|
|
* @var Doctrine_Table_Repository $repository record repository
|
|
*/
|
|
private $repository;
|
|
/**
|
|
* @var array $columns an array of column definitions,
|
|
* keys as column names and values as column definitions
|
|
*
|
|
* the value array has three values:
|
|
*
|
|
* the column type, eg. 'integer'
|
|
* the column length, eg. 11
|
|
* the column options/constraints/validators. eg array('notnull' => true)
|
|
*
|
|
* so the full columns array might look something like the following:
|
|
* array(
|
|
* 'name' => array('string', 20, array('notnull' => true, 'default' => 'someone')),
|
|
* 'age' => array('integer', 11, array('notnull' => true))
|
|
* )
|
|
*/
|
|
protected $columns = array();
|
|
/**
|
|
* @var array $columnAliases an array of column aliases
|
|
* keys as column aliases and values as column names
|
|
*/
|
|
protected $columnAliases = array();
|
|
/**
|
|
* @var array $bound bound relations
|
|
*/
|
|
private $bound = array();
|
|
/**
|
|
* @var array $boundAliases bound relation aliases
|
|
*/
|
|
private $boundAliases = array();
|
|
/**
|
|
* @var integer $columnCount cached column count, Doctrine_Record uses this column count in when
|
|
* determining its state
|
|
*/
|
|
private $columnCount;
|
|
/**
|
|
* @var boolean $hasDefaultValues whether or not this table has default values
|
|
*/
|
|
private $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
|
|
*
|
|
* -- collation
|
|
*
|
|
* -- indexes the index definitions of this table
|
|
*
|
|
* -- treeImpl the tree implementation of this table (if any)
|
|
*
|
|
* -- treeOptions the tree options
|
|
*/
|
|
protected $options = array('name' => null,
|
|
'tableName' => null,
|
|
'sequenceName' => null,
|
|
'inheritanceMap' => array(),
|
|
'enumMap' => array(),
|
|
'engine' => null,
|
|
'charset' => null,
|
|
'collation' => null,
|
|
'treeImpl' => null,
|
|
'treeOptions' => null,
|
|
'indexes' => array(),
|
|
);
|
|
/**
|
|
* @var Doctrine_Tree $tree tree object associated with this table
|
|
*/
|
|
protected $tree;
|
|
/**
|
|
* the constructor
|
|
* @throws Doctrine_Connection_Exception if there are no opened connections
|
|
* @throws Doctrine_Table_Exception if there is already an instance of this table
|
|
* @return void
|
|
*/
|
|
public function __construct($name, Doctrine_Connection $conn)
|
|
{
|
|
$this->conn = $conn;
|
|
|
|
$this->setParent($this->conn);
|
|
|
|
$this->options['name'] = $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));
|
|
|
|
// reverse names
|
|
$names = array_reverse($names);
|
|
|
|
// create database table
|
|
if (method_exists($record, 'setTableDefinition')) {
|
|
$record->setTableDefinition();
|
|
|
|
// set the table definition for the given tree implementation
|
|
if($this->isTree())
|
|
$this->getTree()->setTableDefinition();
|
|
|
|
$this->columnCount = count($this->columns);
|
|
|
|
if (isset($this->columns)) {
|
|
// get the declaring class of setTableDefinition method
|
|
$method = new ReflectionMethod($this->options['name'], 'setTableDefinition');
|
|
$class = $method->getDeclaringClass();
|
|
|
|
$this->options['declaringClass'] = $class;
|
|
|
|
if ( ! isset($this->options['tableName'])) {
|
|
$this->options['tableName'] = Doctrine::tableize($class->getName());
|
|
}
|
|
switch (count($this->primaryKeys)) {
|
|
case 0:
|
|
$this->columns = array_merge(array('id' =>
|
|
array('integer',
|
|
20,
|
|
array('autoincrement' => true,
|
|
'primary' => true,
|
|
)
|
|
)
|
|
), $this->columns);
|
|
|
|
$this->primaryKeys[] = 'id';
|
|
$this->identifier = 'id';
|
|
$this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
|
|
$this->columnCount++;
|
|
break;
|
|
default:
|
|
if (count($this->primaryKeys) > 1) {
|
|
$this->identifier = $this->primaryKeys;
|
|
$this->identifierType = Doctrine_Identifier::COMPOSITE;
|
|
|
|
} else {
|
|
foreach ($this->primaryKeys as $pk) {
|
|
$e = $this->columns[$pk][2];
|
|
|
|
$found = false;
|
|
|
|
foreach ($e as $option => $value) {
|
|
if ($found)
|
|
break;
|
|
|
|
$e2 = explode(':', $option);
|
|
|
|
switch (strtolower($e2[0])) {
|
|
case 'autoincrement':
|
|
case 'autoinc':
|
|
$this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
|
|
$found = true;
|
|
break;
|
|
case 'seq':
|
|
case 'sequence':
|
|
$this->identifierType = Doctrine_Identifier::SEQUENCE;
|
|
$found = true;
|
|
|
|
if ($value) {
|
|
$this->options['sequenceName'] = $value;
|
|
} else {
|
|
if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) {
|
|
$this->options['sequenceName'] = $sequence;
|
|
} else {
|
|
$this->options['sequenceName'] = $this->conn->getSequenceName($this->options['tableName']);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if ( ! isset($this->identifierType)) {
|
|
$this->identifierType = Doctrine_Identifier::NORMAL;
|
|
}
|
|
$this->identifier = $pk;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw new Doctrine_Table_Exception("Class '$name' has no table definition.");
|
|
}
|
|
|
|
$record->setUp();
|
|
|
|
// if tree, set up tree
|
|
if ($this->isTree()) {
|
|
$this->getTree()->setUp();
|
|
}
|
|
|
|
// save parents
|
|
array_pop($names);
|
|
$this->options['parents'] = $names;
|
|
|
|
$this->repository = new Doctrine_Table_Repository($this);
|
|
}
|
|
/**
|
|
* export
|
|
* exports this table to database based on column and option definitions
|
|
*
|
|
* @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
|
|
* occurred during the create table operation
|
|
* @return boolean whether or not the export operation was successful
|
|
* false if table already existed in the database
|
|
*/
|
|
public function export()
|
|
{
|
|
if ( ! Doctrine::isValidClassname($this->options['declaringClass']->getName())) {
|
|
throw new Doctrine_Table_Exception('Class name not valid.');
|
|
}
|
|
|
|
try {
|
|
$columns = array();
|
|
$primary = array();
|
|
|
|
foreach ($this->columns as $name => $column) {
|
|
$definition = $column[2];
|
|
$definition['type'] = $column[0];
|
|
$definition['length'] = $column[1];
|
|
|
|
switch ($definition['type']) {
|
|
case 'enum':
|
|
if (isset($definition['default'])) {
|
|
$definition['default'] = $this->enumIndex($name, $definition['default']);
|
|
}
|
|
break;
|
|
case 'boolean':
|
|
if (isset($definition['default'])) {
|
|
$definition['default'] = $this->conn->convertBooleans($definition['default']);
|
|
}
|
|
break;
|
|
}
|
|
$columns[$name] = $definition;
|
|
|
|
if(isset($definition['primary']) && $definition['primary']) {
|
|
$primary[] = $name;
|
|
}
|
|
}
|
|
|
|
if ($this->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_CONSTRAINTS) {
|
|
|
|
foreach ($this->getRelations() as $name => $relation) {
|
|
$fk = $relation->toArray();
|
|
$fk['foreignTable'] = $relation->getTable()->getTableName();
|
|
|
|
if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
|
|
continue;
|
|
}
|
|
|
|
if ($relation->hasConstraint()) {
|
|
|
|
$options['foreignKeys'][] = $fk;
|
|
} elseif ($relation instanceof Doctrine_Relation_LocalKey) {
|
|
$options['foreignKeys'][] = $fk;
|
|
}
|
|
}
|
|
}
|
|
|
|
$options['primary'] = $primary;
|
|
|
|
$this->conn->export->createTable($this->options['tableName'], $columns, array_merge($this->options, $options));
|
|
} catch(Doctrine_Connection_Exception $e) {
|
|
// we only want to silence table already exists errors
|
|
if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* exportConstraints
|
|
* exports the constraints of this table into database based on option definitions
|
|
*
|
|
* @throws Doctrine_Connection_Exception if something went wrong on db level
|
|
* @return void
|
|
*/
|
|
public function exportConstraints()
|
|
{
|
|
try {
|
|
$this->conn->beginTransaction();
|
|
|
|
foreach ($this->options['index'] as $index => $definition) {
|
|
$this->conn->export->createIndex($this->options['tableName'], $index, $definition);
|
|
}
|
|
$this->conn->commit();
|
|
} catch(Doctrine_Connection_Exception $e) {
|
|
$this->conn->rollback();
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
/**
|
|
* __get
|
|
* an alias for getOption
|
|
*
|
|
* @param string $option
|
|
*/
|
|
public function __get($option)
|
|
{
|
|
if (isset($this->options[$option])) {
|
|
return $this->options[$option];
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* __isset
|
|
*
|
|
* @param string $option
|
|
*/
|
|
public function __isset($option)
|
|
{
|
|
return isset($this->options[$option]);
|
|
}
|
|
/**
|
|
* addForeignKey
|
|
*
|
|
* adds a foreignKey to this table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function addForeignKey(array $definition)
|
|
{
|
|
$this->options['foreignKeys'][] = $definition;
|
|
}
|
|
/**
|
|
* addIndex
|
|
*
|
|
* adds an index to this table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function addIndex($index, array $definition)
|
|
{
|
|
$index = $this->conn->getIndexName($index);
|
|
$this->options['indexes'][$index] = $definition;
|
|
}
|
|
/**
|
|
* getIndex
|
|
*
|
|
* @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;
|
|
}
|
|
/**
|
|
* createQuery
|
|
* creates a new Doctrine_Query object and adds the component name
|
|
* of this table as the query 'from' part
|
|
*
|
|
* @return Doctrine_Query
|
|
*/
|
|
public function createQuery()
|
|
{
|
|
return Doctrine_Query::create()->from($this->getComponentName());
|
|
}
|
|
/**
|
|
* getRepository
|
|
*
|
|
* @return Doctrine_Table_Repository
|
|
*/
|
|
public function getRepository()
|
|
{
|
|
return $this->repository;
|
|
}
|
|
|
|
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;
|
|
}
|
|
public function getOption($name)
|
|
{
|
|
if (isset($this->options[$name])) {
|
|
return $this->options[$name];
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* getColumnName
|
|
*
|
|
* returns a column name for 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($alias)
|
|
{
|
|
if(isset($this->columnAliases[$alias])) {
|
|
return $this->columnAliases[$alias];
|
|
}
|
|
|
|
return $alias;
|
|
}
|
|
/**
|
|
* setColumn
|
|
*
|
|
* @param string $name
|
|
* @param string $type
|
|
* @param integer $length
|
|
* @param mixed $options
|
|
* @throws Doctrine_Table_Exception if trying use wrongly typed parameter
|
|
* @return void
|
|
*/
|
|
public function setColumn($name, $type, $length = null, $options = array())
|
|
{
|
|
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]);
|
|
}
|
|
}
|
|
|
|
$name = strtolower($name);
|
|
$parts = explode(' as ', $name);
|
|
|
|
if (count($parts) > 1) {
|
|
$this->columnAliases[$parts[1]] = $parts[0];
|
|
$name = $parts[0];
|
|
}
|
|
|
|
|
|
if ($length == null) {
|
|
$length = 2147483647;
|
|
}
|
|
|
|
if ((string) (int) $length !== (string) $length) {
|
|
throw new Doctrine_Table_Exception('Invalid argument given for column length');
|
|
}
|
|
|
|
$this->columns[$name] = array($type, $length, $options);
|
|
|
|
if (isset($options['primary'])) {
|
|
$this->primaryKeys[] = $name;
|
|
}
|
|
if (isset($options['default'])) {
|
|
$this->hasDefaultValues = true;
|
|
}
|
|
}
|
|
/**
|
|
* hasDefaultValues
|
|
* returns true if this table has default values, otherwise false
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasDefaultValues()
|
|
{
|
|
return $this->hasDefaultValues;
|
|
}
|
|
/**
|
|
* getDefaultValueOf
|
|
* returns the default value(if any) for given column
|
|
*
|
|
* @param string $column
|
|
* @return mixed
|
|
*/
|
|
public function getDefaultValueOf($column)
|
|
{
|
|
$column = strtolower($column);
|
|
if ( ! isset($this->columns[$column])) {
|
|
throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$column." doesn't exist.");
|
|
}
|
|
if (isset($this->columns[$column][2]['default'])) {
|
|
return $this->columns[$column][2]['default'];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getIdentifier()
|
|
{
|
|
return $this->identifier;
|
|
}
|
|
/**
|
|
* @return integer
|
|
*/
|
|
public function getIdentifierType()
|
|
{
|
|
return $this->identifierType;
|
|
}
|
|
/**
|
|
* hasColumn
|
|
* @return boolean
|
|
*/
|
|
public function hasColumn($name)
|
|
{
|
|
return isset($this->columns[$name]);
|
|
}
|
|
/**
|
|
* @param mixed $key
|
|
* @return void
|
|
*/
|
|
public function setPrimaryKey($key)
|
|
{
|
|
switch (gettype($key)) {
|
|
case "array":
|
|
$this->primaryKeys = array_values($key);
|
|
break;
|
|
case "string":
|
|
$this->primaryKeys[] = $key;
|
|
break;
|
|
};
|
|
}
|
|
/**
|
|
* returns all primary keys
|
|
* @return array
|
|
*/
|
|
public function getPrimaryKeys()
|
|
{
|
|
return $this->primaryKeys;
|
|
}
|
|
/**
|
|
* @return boolean
|
|
*/
|
|
public function hasPrimaryKey($key)
|
|
{
|
|
return in_array($key,$this->primaryKeys);
|
|
}
|
|
/**
|
|
* returns all bound relations
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getBounds()
|
|
{
|
|
return $this->bound;
|
|
}
|
|
/**
|
|
* returns a bound relation array
|
|
*
|
|
* @param string $name
|
|
* @return array
|
|
*/
|
|
public function getBound($name)
|
|
{
|
|
if ( ! isset($this->bound[$name])) {
|
|
throw new Doctrine_Table_Exception('Unknown bound ' . $name);
|
|
}
|
|
return $this->bound[$name];
|
|
}
|
|
/**
|
|
* returns a bound relation array
|
|
*
|
|
* @param string $name
|
|
* @return array
|
|
*/
|
|
public function getBoundForName($name, $component)
|
|
{
|
|
foreach ($this->bound as $k => $bound) {
|
|
$e = explode('.', $bound['field']);
|
|
|
|
if ($bound['class'] == $name && $e[0] == $component) {
|
|
return $this->bound[$k];
|
|
}
|
|
}
|
|
throw new Doctrine_Table_Exception('Unknown bound ' . $name);
|
|
}
|
|
/**
|
|
* returns the alias for given component name
|
|
*
|
|
* @param string $name
|
|
* @return string
|
|
*/
|
|
public function getAlias($name)
|
|
{
|
|
if (isset($this->boundAliases[$name])) {
|
|
return $this->boundAliases[$name];
|
|
}
|
|
return $name;
|
|
}
|
|
/**
|
|
* returns component name for given alias
|
|
*
|
|
* @param string $alias
|
|
* @return string
|
|
*/
|
|
public function getAliasName($alias)
|
|
{
|
|
if ($name = array_search($alias, $this->boundAliases)) {
|
|
return $name;
|
|
}
|
|
return $alias;
|
|
}
|
|
/**
|
|
* unbinds all relations
|
|
*
|
|
* @return void
|
|
*/
|
|
public function unbindAll()
|
|
{ throw new Exception();
|
|
$this->bound = array();
|
|
$this->relations = array();
|
|
$this->boundAliases = array();
|
|
}
|
|
/**
|
|
* unbinds a relation
|
|
* returns true on success, false on failure
|
|
*
|
|
* @param $name
|
|
* @return boolean
|
|
*/
|
|
public function unbind($name)
|
|
{
|
|
if ( ! isset($this->bound[$name])) {
|
|
return false;
|
|
}
|
|
unset($this->bound[$name]);
|
|
|
|
if (isset($this->relations[$name])) {
|
|
unset($this->relations[$name]);
|
|
}
|
|
if (isset($this->boundAliases[$name])) {
|
|
unset($this->boundAliases[$name]);
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* binds a relation
|
|
*
|
|
* @param string $name
|
|
* @param string $field
|
|
* @return void
|
|
*/
|
|
public function bind($name, $field, $type, $options = null)
|
|
{
|
|
if (isset($this->relations[$name])) {
|
|
unset($this->relations[$name]);
|
|
}
|
|
|
|
$lower = strtolower($name);
|
|
|
|
if (isset($this->columns[$lower])) {
|
|
throw new Doctrine_Table_Exception("Couldn't bind relation. Column with name " . $lower . ' already exists!');
|
|
}
|
|
|
|
$e = explode(' as ', $name);
|
|
$name = $e[0];
|
|
|
|
if (isset($e[1])) {
|
|
$alias = $e[1];
|
|
$this->boundAliases[$name] = $alias;
|
|
} else {
|
|
$alias = $name;
|
|
}
|
|
|
|
$this->bound[$alias] = array('field' => $field,
|
|
'type' => $type,
|
|
'class' => $name,
|
|
'alias' => $alias);
|
|
if ($options !== null) {
|
|
$opt = array();
|
|
if (is_string($options)) {
|
|
$opt['local'] = $options;
|
|
} else {
|
|
$opt = (array) $options;
|
|
}
|
|
|
|
$this->bound[$alias] = array_merge($this->bound[$alias], $opt);
|
|
}
|
|
}
|
|
/**
|
|
* @return Doctrine_Connection
|
|
*/
|
|
public function getConnection()
|
|
{
|
|
return $this->conn;
|
|
}
|
|
/**
|
|
* hasRelatedComponent
|
|
* @return boolean
|
|
*/
|
|
public function hasRelatedComponent($name, $component)
|
|
{
|
|
return (strpos($this->bound[$name]['field'], $component . '.') !== false);
|
|
}
|
|
/**
|
|
* @param string $name component name of which a foreign key object is bound
|
|
* @return boolean
|
|
*/
|
|
final public function hasRelation($name)
|
|
{
|
|
if (isset($this->bound[$name])) {
|
|
return true;
|
|
}
|
|
foreach ($this->bound as $k=>$v) {
|
|
if ($this->hasRelatedComponent($k, $name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* getRelation
|
|
*
|
|
* @param string $name component name of which a foreign key object is bound
|
|
* @return Doctrine_Relation
|
|
*/
|
|
public function getRelation($name, $recursive = true)
|
|
{
|
|
if (isset($this->relations[$name])) {
|
|
return $this->relations[$name];
|
|
}
|
|
|
|
if ( ! $this->conn->hasTable($this->options['name'])) {
|
|
$allowExport = true;
|
|
} else {
|
|
$allowExport = false;
|
|
}
|
|
|
|
if (isset($this->bound[$name])) {
|
|
|
|
$definition = $this->bound[$name];
|
|
|
|
list($component, $definition['foreign']) = explode('.', $definition['field']);
|
|
unset($definition['field']);
|
|
|
|
$definition['table'] = $this->conn->getTable($definition['class'], $allowExport);
|
|
$definition['constraint'] = false;
|
|
|
|
if ($component == $this->options['name'] || in_array($component, $this->options['parents'])) {
|
|
|
|
// ONE-TO-ONE
|
|
if ($definition['type'] == Doctrine_Relation::ONE_COMPOSITE ||
|
|
$definition['type'] == Doctrine_Relation::ONE_AGGREGATE) {
|
|
// tree structure parent relation found
|
|
|
|
if ( ! isset($definition['local'])) {
|
|
$definition['local'] = $definition['foreign'];
|
|
$definition['foreign'] = $definition['table']->getIdentifier();
|
|
}
|
|
|
|
$relation = new Doctrine_Relation_LocalKey($definition);
|
|
|
|
} else {
|
|
// tree structure children relation found
|
|
|
|
if ( ! isset($definition['local'])) {
|
|
$tmp = $definition['table']->getIdentifier();
|
|
|
|
$definition['local'] = $tmp;
|
|
}
|
|
|
|
//$definition['foreign'] = $tmp;
|
|
$definition['constraint'] = true;
|
|
|
|
$relation = new Doctrine_Relation_ForeignKey($definition);
|
|
}
|
|
|
|
} elseif ($component == $definition['class'] ||
|
|
($component == $definition['alias'])) { // && ($name == $this->options['name'] || in_array($name,$this->parents))
|
|
|
|
if ( ! isset($defintion['local'])) {
|
|
$definition['local'] = $this->identifier;
|
|
}
|
|
|
|
$definition['constraint'] = true;
|
|
|
|
// ONE-TO-MANY or ONE-TO-ONE
|
|
$relation = new Doctrine_Relation_ForeignKey($definition);
|
|
|
|
} else {
|
|
// MANY-TO-MANY
|
|
// only aggregate relations allowed
|
|
|
|
if ($definition['type'] != Doctrine_Relation::MANY_AGGREGATE) {
|
|
throw new Doctrine_Table_Exception("Only aggregate relations are allowed for many-to-many relations");
|
|
}
|
|
|
|
$classes = array_merge($this->options['parents'], array($this->options['name']));
|
|
|
|
foreach (array_reverse($classes) as $class) {
|
|
try {
|
|
$bound = $definition['table']->getBoundForName($class, $component);
|
|
break;
|
|
} catch(Doctrine_Table_Exception $exc) { }
|
|
}
|
|
if ( ! isset($bound)) {
|
|
throw new Doctrine_Table_Exception("Couldn't map many-to-many relation for "
|
|
. $this->options['name'] . " and $name. Components use different join tables.");
|
|
}
|
|
if ( ! isset($definition['local'])) {
|
|
$definition['local'] = $this->identifier;
|
|
}
|
|
$e2 = explode('.', $bound['field']);
|
|
$fields = explode('-', $e2[1]);
|
|
|
|
if ($e2[0] != $component) {
|
|
throw new Doctrine_Table_Exception($e2[0] . ' doesn\'t match ' . $component);
|
|
}
|
|
$associationTable = $this->conn->getTable($e2[0], $allowExport);
|
|
|
|
if (count($fields) > 1) {
|
|
// SELF-REFERENCING THROUGH JOIN TABLE
|
|
|
|
$def['table'] = $associationTable;
|
|
$def['local'] = $this->identifier;
|
|
$def['foreign'] = $fields[0];
|
|
$def['alias'] = $e2[0];
|
|
$def['class'] = $e2[0];
|
|
$def['type'] = Doctrine_Relation::MANY_COMPOSITE;
|
|
|
|
$this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($def);
|
|
|
|
$definition['assocTable'] = $associationTable;
|
|
$definition['local'] = $fields[0];
|
|
$definition['foreign'] = $fields[1];
|
|
$relation = new Doctrine_Relation_Association_Self($definition);
|
|
} else {
|
|
if($definition['table'] === $this) {
|
|
|
|
} else {
|
|
// auto initialize a new one-to-one relationships for association table
|
|
$associationTable->bind($this->getComponentName(),
|
|
$associationTable->getComponentName(). '.' . $e2[1],
|
|
Doctrine_Relation::ONE_AGGREGATE
|
|
);
|
|
|
|
$associationTable->bind($definition['table']->getComponentName(),
|
|
$associationTable->getComponentName(). '.' . $definition['foreign'],
|
|
Doctrine_Relation::ONE_AGGREGATE
|
|
);
|
|
|
|
// NORMAL MANY-TO-MANY RELATIONSHIP
|
|
|
|
$def['table'] = $associationTable;
|
|
$def['foreign'] = $e2[1];
|
|
$def['local'] = $definition['local'];
|
|
$def['alias'] = $e2[0];
|
|
$def['class'] = $e2[0];
|
|
$def['type'] = Doctrine_Relation::MANY_COMPOSITE;
|
|
$this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($def);
|
|
|
|
$definition['local'] = $e2[1];
|
|
$definition['assocTable'] = $associationTable;
|
|
$relation = new Doctrine_Relation_Association($definition);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->relations[$name] = $relation;
|
|
|
|
return $this->relations[$name];
|
|
}
|
|
|
|
|
|
// load all relations
|
|
$this->getRelations();
|
|
|
|
if ($recursive) {
|
|
return $this->getRelation($name, false);
|
|
} else {
|
|
throw new Doctrine_Table_Exception($this->options['name'] . " doesn't have a relation to " . $name);
|
|
}
|
|
|
|
}
|
|
/**
|
|
* returns an array containing all foreign key objects
|
|
*
|
|
* @return array
|
|
*/
|
|
final public function getRelations()
|
|
{
|
|
foreach ($this->bound as $k => $v) {
|
|
$this->getRelation($k);
|
|
}
|
|
|
|
return $this->relations;
|
|
}
|
|
/**
|
|
* create
|
|
* creates a new record
|
|
*
|
|
* @param $array an array where keys are field names and values representing field values
|
|
* @return Doctrine_Record
|
|
*/
|
|
public function create(array $array = array()) {
|
|
$this->data = $array;
|
|
$record = new $this->options['name']($this, true);
|
|
$this->data = array();
|
|
return $record;
|
|
}
|
|
/**
|
|
* finds a record by its identifier
|
|
*
|
|
* @param $id database row id
|
|
* @return Doctrine_Record|false a record for given database identifier
|
|
*/
|
|
public function find($id)
|
|
{
|
|
if ($id !== null) {
|
|
if ( ! is_array($id)) {
|
|
$id = array($id);
|
|
} else {
|
|
$id = array_values($id);
|
|
}
|
|
|
|
$query = 'SELECT ' . implode(', ', array_keys($this->columns)) . ' FROM ' . $this->getTableName() . ' WHERE ' . implode(' = ? AND ', $this->primaryKeys) . ' = ?';
|
|
$query = $this->applyInheritance($query);
|
|
|
|
$params = array_merge($id, array_values($this->options['inheritanceMap']));
|
|
|
|
$stmt = $this->conn->execute($query, $params);
|
|
|
|
$this->data = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($this->data === false)
|
|
return false;
|
|
|
|
return $this->getRecord();
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* 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[] = $field . ' = ?';
|
|
}
|
|
$i = implode(' AND ', $a);
|
|
$where .= ' AND ' . $i;
|
|
}
|
|
return $where;
|
|
}
|
|
/**
|
|
* findAll
|
|
* returns a collection of records
|
|
*
|
|
* @return Doctrine_Collection
|
|
*/
|
|
public function findAll()
|
|
{
|
|
$graph = new Doctrine_Query($this->conn);
|
|
$users = $graph->query("FROM ".$this->options['name']);
|
|
return $users;
|
|
}
|
|
/**
|
|
* findByDql
|
|
* finds records with given DQL where clause
|
|
* returns a collection of records
|
|
*
|
|
* @param string $dql DQL after WHERE clause
|
|
* @param array $params query parameters
|
|
* @return Doctrine_Collection
|
|
*/
|
|
public function findBySql($dql, array $params = array()) {
|
|
$q = new Doctrine_Query($this->conn);
|
|
$users = $q->query("FROM ".$this->options['name']." WHERE ".$dql, $params);
|
|
return $users;
|
|
}
|
|
|
|
public function findByDql($dql, array $params = array()) {
|
|
return $this->findBySql($dql, $params);
|
|
}
|
|
/**
|
|
* clear
|
|
* clears the first level cache (identityMap)
|
|
*
|
|
* @return void
|
|
*/
|
|
public function clear()
|
|
{
|
|
$this->identityMap = array();
|
|
}
|
|
/**
|
|
* getRecord
|
|
* first checks if record exists in identityMap, if not
|
|
* returns a new record
|
|
*
|
|
* @return Doctrine_Record
|
|
*/
|
|
public function getRecord()
|
|
{
|
|
$this->data = array_change_key_case($this->data, CASE_LOWER);
|
|
|
|
$key = $this->getIdentifier();
|
|
|
|
if ( ! is_array($key)) {
|
|
$key = array($key);
|
|
}
|
|
|
|
foreach ($key as $k) {
|
|
if ( ! isset($this->data[$k])) {
|
|
throw new Doctrine_Table_Exception("Primary key value for $k wasn't found");
|
|
}
|
|
$id[] = $this->data[$k];
|
|
}
|
|
|
|
$id = implode(' ', $id);
|
|
|
|
if (isset($this->identityMap[$id])) {
|
|
$record = $this->identityMap[$id];
|
|
$record->hydrate($this->data);
|
|
} else {
|
|
$recordName = $this->getClassnameToReturn();
|
|
$record = new $recordName($this);
|
|
$this->identityMap[$id] = $record;
|
|
}
|
|
$this->data = array();
|
|
|
|
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
|
|
*
|
|
*/
|
|
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
|
|
*/
|
|
final public function getProxy($id = null)
|
|
{
|
|
if ($id !== null) {
|
|
$query = 'SELECT ' . implode(', ',$this->primaryKeys)
|
|
. ' FROM ' . $this->getTableName()
|
|
. ' WHERE ' . implode(' = ? && ',$this->primaryKeys).' = ?';
|
|
$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();
|
|
}
|
|
/**
|
|
* count
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function count()
|
|
{
|
|
$a = $this->conn->getDBH()->query("SELECT COUNT(1) FROM ".$this->options['tableName'])->fetch(PDO::FETCH_NUM);
|
|
return current($a);
|
|
}
|
|
/**
|
|
* @return Doctrine_Query a Doctrine_Query object
|
|
*/
|
|
public function getQueryObject()
|
|
{
|
|
$graph = new Doctrine_Query($this->getConnection());
|
|
$graph->load($this->getComponentName());
|
|
return $graph;
|
|
}
|
|
/**
|
|
* execute
|
|
* @param string $query
|
|
* @param array $array
|
|
* @param integer $limit
|
|
* @param integer $offset
|
|
*/
|
|
public function execute($query, array $array = array(), $limit = null, $offset = null) {
|
|
$coll = new Doctrine_Collection($this);
|
|
$query = $this->conn->modifyLimitQuery($query,$limit,$offset);
|
|
if ( ! empty($array)) {
|
|
$stmt = $this->conn->getDBH()->prepare($query);
|
|
$stmt->execute($array);
|
|
} else {
|
|
$stmt = $this->conn->getDBH()->query($query);
|
|
}
|
|
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
$stmt->closeCursor();
|
|
|
|
foreach ($data as $row) {
|
|
$this->data = $row;
|
|
$record = $this->getRecord();
|
|
$coll->add($record);
|
|
}
|
|
return $coll;
|
|
}
|
|
/**
|
|
* @param string $field
|
|
* @return array
|
|
*/
|
|
final public function getEnumValues($field)
|
|
{
|
|
if (isset($this->columns[$field][2]['values'])) {
|
|
return $this->columns[$field][2]['values'];
|
|
} else {
|
|
return array();
|
|
}
|
|
}
|
|
/**
|
|
* enumValue
|
|
*
|
|
* @param string $field
|
|
* @param integer $index
|
|
* @return mixed
|
|
*/
|
|
public function enumValue($field, $index)
|
|
{
|
|
if ($index instanceof Doctrine_Null)
|
|
return $index;
|
|
|
|
return isset($this->columns[$field][2]['values'][$index]) ? $this->columns[$field][2]['values'][$index] : $index;
|
|
}
|
|
/**
|
|
* enumIndex
|
|
*
|
|
* @param string $field
|
|
* @param mixed $value
|
|
* @return mixed
|
|
*/
|
|
public function enumIndex($field, $value)
|
|
{
|
|
$values = $this->getEnumValues($field);
|
|
|
|
return array_search($value, $values);
|
|
}
|
|
/**
|
|
* invokeSet
|
|
*
|
|
* @param mixed $value
|
|
*/
|
|
public function invokeSet(Doctrine_Record $record, $name, $value)
|
|
{
|
|
if ( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_SET)) {
|
|
return $value;
|
|
}
|
|
$prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_SET);
|
|
if (!$prefix)
|
|
$prefix = 'set';
|
|
|
|
$method = $prefix . $name;
|
|
|
|
if (method_exists($record, $method)) {
|
|
return $record->$method($value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
/**
|
|
* invokeGet
|
|
*
|
|
* @param mixed $value
|
|
*/
|
|
public function invokeGet(Doctrine_Record $record, $name, $value)
|
|
{
|
|
if ( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_GET)) {
|
|
return $value;
|
|
}
|
|
$prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_GET);
|
|
if (!$prefix)
|
|
$prefix = 'get';
|
|
|
|
$method = $prefix . $name;
|
|
|
|
if (method_exists($record, $method)) {
|
|
return $record->$method($value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* getDefinitionOf
|
|
*
|
|
* @return string ValueWrapper class name on success, false on failure
|
|
*/
|
|
public function getValueWrapperOf($column)
|
|
{
|
|
if (isset($this->columns[$column][2]['wrapper'])) {
|
|
return $this->columns[$column][2]['wrapper'];
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* getColumnCount
|
|
*
|
|
* @return integer the number of columns in this table
|
|
*/
|
|
final public function getColumnCount()
|
|
{
|
|
return $this->columnCount;
|
|
}
|
|
|
|
/**
|
|
* returns all columns and their definitions
|
|
*
|
|
* @return array
|
|
*/
|
|
final public function getColumns()
|
|
{
|
|
return $this->columns;
|
|
}
|
|
/**
|
|
* returns an array containing all the column names
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getColumnNames()
|
|
{
|
|
return array_keys($this->columns);
|
|
}
|
|
|
|
/**
|
|
* getDefinitionOf
|
|
*
|
|
* @return mixed array on success, false on failure
|
|
*/
|
|
public function getDefinitionOf($column)
|
|
{
|
|
if (isset($this->columns[$column])) {
|
|
return $this->columns[$column];
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* getTypeOf
|
|
*
|
|
* @return mixed string on success, false on failure
|
|
*/
|
|
public function getTypeOf($column)
|
|
{
|
|
if (isset($this->columns[$column])) {
|
|
return $this->columns[$column][0];
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* setData
|
|
* doctrine uses this function internally
|
|
* users are strongly discouraged to use this function
|
|
*
|
|
* @param array $data internal data
|
|
* @return void
|
|
*/
|
|
public function setData(array $data)
|
|
{
|
|
$this->data = $data;
|
|
}
|
|
/**
|
|
* returns the maximum primary key value
|
|
*
|
|
* @return integer
|
|
*/
|
|
final public function getMaxIdentifier()
|
|
{
|
|
$sql = "SELECT MAX(".$this->getIdentifier().") FROM ".$this->getTableName();
|
|
$stmt = $this->conn->getDBH()->query($sql);
|
|
$data = $stmt->fetch(PDO::FETCH_NUM);
|
|
return isset($data[0])?$data[0]:1;
|
|
}
|
|
/**
|
|
* returns internal data, used by Doctrine_Record instances
|
|
* when retrieving data from database
|
|
*
|
|
* @return array
|
|
*/
|
|
final public function getData()
|
|
{
|
|
return $this->data;
|
|
}
|
|
/**
|
|
* getter for associated tree
|
|
*
|
|
* @return mixed if tree return instance of Doctrine_Tree, otherwise returns false
|
|
*/
|
|
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;
|
|
}
|
|
public function getComponentName()
|
|
{
|
|
return $this->options['name'];
|
|
}
|
|
public function getTableName()
|
|
{
|
|
return $this->options['tableName'];
|
|
}
|
|
public function setTableName($tableName)
|
|
{
|
|
$this->options['tableName'] = $tableName;
|
|
}
|
|
/**
|
|
* determine if table acts as tree
|
|
*
|
|
* @return mixed if tree return true, otherwise returns false
|
|
*/
|
|
public function isTree() {
|
|
return ( ! is_null($this->options['treeImpl'])) ? true : false;
|
|
}
|
|
/**
|
|
* returns a string representation of this object
|
|
*
|
|
* @return string
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return Doctrine_Lib::getTableAsString($this);
|
|
}
|
|
}
|