Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/** $Id: Table.php 1397 2007-05-19 19:54:15Z 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.doctrine-project.org>.*//*** Doctrine_Relation_Parser** @package Doctrine* @subpackage Relation* @author Konsta Vesterinen <kvesteri@cc.hut.fi>* @license http://www.opensource.org/licenses/lgpl-license.php LGPL* @version $Revision: 1397 $* @link www.doctrine-project.org* @since 1.0* @todo Composite key support?*/class Doctrine_Relation_Parser{/*** @var Doctrine_Table $_table the table object this parser belongs to*/protected $_table;/*** @var array $_relations an array containing all the Doctrine_Relation objects for this table*/protected $_relations = array();/*** @var array $_pending relations waiting for parsing*/protected $_pending = array();/*** constructor** @param Doctrine_Table $table the table object this parser belongs to*/public function __construct(Doctrine_Table $table){$this->_table = $table;}/*** getTable** @return Doctrine_Table the table object this parser belongs to*/public function getTable(){return $this->_table;}/*** getPendingRelation** @return array an array defining a pending relation*/public function getPendingRelation($name){if ( ! isset($this->_pending[$name])) {throw new Doctrine_Relation_Exception('Unknown pending relation ' . $name);}return $this->_pending[$name];}/*** getPendingRelations** @return array an array containing all the pending relations*/public function getPendingRelations(){return $this->_pending;}/*** unsetPendingRelations* Removes a relation. Warning: this only affects pending relations** @param string relation to remove*/public function unsetPendingRelations($name){unset($this->_pending[$name]);}/*** Check if a relation alias exists** @param string $name* @return boolean $bool*/public function hasRelation($name){if ( ! isset($this->_pending[$name]) && ! isset($this->_relations[$name])) {return false;}return true;}/*** binds a relation** @param string $name* @param string $field* @return void*/public function bind($name, $options = array()){$e = explode(' as ', $name);$e = array_map('trim', $e);$name = $e[0];$alias = isset($e[1]) ? $e[1] : $name;if ( ! isset($options['type'])) {throw new Doctrine_Relation_Exception('Relation type not set.');}if ($this->hasRelation($alias)) {unset($this->_relations[$alias]);unset($this->_pending[$alias]);}$this->_pending[$alias] = array_merge($options, array('class' => $name, 'alias' => $alias));return $this->_pending[$alias];}/*** getRelation** @param string $alias relation alias*/public function getRelation($alias, $recursive = true){if (isset($this->_relations[$alias])) {return $this->_relations[$alias];}if (isset($this->_pending[$alias])) {$def = $this->_pending[$alias];$identifierColumnNames = $this->_table->getIdentifierColumnNames();$idColumnName = array_pop($identifierColumnNames);// check if reference class name exists// if it does we are dealing with association relationif (isset($def['refClass'])) {$def = $this->completeAssocDefinition($def);$localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));$backRefRelationName = isset($def['refClassRelationAlias']) ?$def['refClassRelationAlias'] : $def['refClass'];if ( ! isset($this->_pending[$backRefRelationName]) && ! isset($this->_relations[$backRefRelationName])) {$parser = $def['refTable']->getRelationParser();if ( ! $parser->hasRelation($this->_table->getComponentName())) {$parser->bind($this->_table->getComponentName(),array('type' => Doctrine_Relation::ONE,'local' => $def['local'],'foreign' => $idColumnName,'localKey' => true,));}if ( ! $this->hasRelation($backRefRelationName)) {if (in_array($def['class'], $localClasses)) {$this->bind($def['refClass'] . " as " . $backRefRelationName, array('type' => Doctrine_Relation::MANY,'foreign' => $def['foreign'],'local' => $idColumnName));} else {$this->bind($def['refClass'] . " as " . $backRefRelationName, array('type' => Doctrine_Relation::MANY,'foreign' => $def['local'],'local' => $idColumnName));}}}if (in_array($def['class'], $localClasses)) {$rel = new Doctrine_Relation_Nest($def);} else {$rel = new Doctrine_Relation_Association($def);}} else {// simple foreign key relation$def = $this->completeDefinition($def);if (isset($def['localKey']) && $def['localKey']) {$rel = new Doctrine_Relation_LocalKey($def);// Automatically index for foreign keys$foreign = (array) $def['foreign'];foreach ($foreign as $fk) {// Check if its already not indexed (primary key)if ( ! $rel['table']->isIdentifier($rel['table']->getFieldName($fk))) {$rel['table']->addIndex($fk, array('fields' => array($fk)));}}} else {$rel = new Doctrine_Relation_ForeignKey($def);}}if (isset($rel)) {// unset pending relationunset($this->_pending[$alias]);$this->_relations[$alias] = $rel;return $rel;}}if ($recursive) {$this->getRelations();return $this->getRelation($alias, false);} else {throw new Doctrine_Table_Exception('Unknown relation alias ' . $alias);}}/*** getRelations* returns an array containing all relation objects** @return array an array of Doctrine_Relation objects*/public function getRelations(){foreach ($this->_pending as $k => $v) {$this->getRelation($k);}return $this->_relations;}/*** getImpl* returns the table class of the concrete implementation for given template* if the given template is not a template then this method just returns the* table class for the given record** @param string $template*/public function getImpl($template){$conn = $this->_table->getConnection();if (class_exists($template) && in_array('Doctrine_Template', class_parents($template))) {$impl = $this->_table->getImpl($template);if ($impl === null) {throw new Doctrine_Relation_Parser_Exception("Couldn't find concrete implementation for template " . $template);}} else {$impl = $template;}return $conn->getTable($impl);}/*** Completes the given association definition** @param array $def definition array to be completed* @return array completed definition array*/public function completeAssocDefinition($def){$conn = $this->_table->getConnection();$def['table'] = $this->getImpl($def['class']);$def['localTable'] = $this->_table;$def['class'] = $def['table']->getComponentName();$def['refTable'] = $this->getImpl($def['refClass']);$id = $def['refTable']->getIdentifierColumnNames();if (count($id) > 1) {if ( ! isset($def['foreign'])) {// foreign key not set// try to guess the foreign key$def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];}if ( ! isset($def['local'])) {// foreign key not set// try to guess the foreign key$def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];}} else {if ( ! isset($def['foreign'])) {// foreign key not set// try to guess the foreign key$columns = $this->getIdentifiers($def['table']);$def['foreign'] = $columns;}if ( ! isset($def['local'])) {// local key not set// try to guess the local key$columns = $this->getIdentifiers($this->_table);$def['local'] = $columns;}}return $def;}/*** getIdentifiers* gives a list of identifiers from given table** the identifiers are in format:* [componentName].[identifier]** @param Doctrine_Table $table table object to retrieve identifiers from*/public function getIdentifiers(Doctrine_Table $table){$componentNameToLower = strtolower($table->getComponentName());if (is_array($table->getIdentifier())) {$columns = array();foreach ((array) $table->getIdentifierColumnNames() as $identColName) {$columns[] = $componentNameToLower . '_' . $identColName;}} else {$columns = $componentNameToLower . '_' . $table->getColumnName($table->getIdentifier());}return $columns;}/*** guessColumns** @param array $classes an array of class names* @param Doctrine_Table $foreignTable foreign table object* @return array an array of column names*/public function guessColumns(array $classes, Doctrine_Table $foreignTable){$conn = $this->_table->getConnection();foreach ($classes as $class) {try {$table = $conn->getTable($class);} catch (Doctrine_Table_Exception $e) {continue;}$columns = $this->getIdentifiers($table);$found = true;foreach ((array) $columns as $column) {if ( ! $foreignTable->hasColumn($column)) {$found = false;break;}}if ($found) {break;}}if ( ! $found) {throw new Doctrine_Relation_Exception("Couldn't find columns.");}return $columns;}/*** Completes the given definition** @param array $def definition array to be completed* @return array completed definition array* @todo Description: What does it mean to complete a definition? What is done (not how)?* Refactor (too long & nesting level)*/public function completeDefinition($def){$conn = $this->_table->getConnection();$def['table'] = $this->getImpl($def['class']);$def['localTable'] = $this->_table;$def['class'] = $def['table']->getComponentName();$foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));$localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));$localIdentifierColumnNames = $this->_table->getIdentifierColumnNames();$localIdentifierCount = count($localIdentifierColumnNames);$localIdColumnName = array_pop($localIdentifierColumnNames);$foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames();$foreignIdColumnName = array_pop($foreignIdentifierColumnNames);if (isset($def['local'])) {$def['local'] = $def['localTable']->getColumnName($def['local']);if ( ! isset($def['foreign'])) {// local key is set, but foreign key is not// try to guess the foreign keyif ($def['local'] === $localIdColumnName) {$def['foreign'] = $this->guessColumns($localClasses, $def['table']);} else {// the foreign field is likely to be the// identifier of the foreign class$def['foreign'] = $foreignIdColumnName;$def['localKey'] = true;}} else {$def['foreign'] = $def['table']->getColumnName($def['foreign']);if ($localIdentifierCount == 1) {if ($def['local'] == $localIdColumnName && isset($def['owningSide'])&& $def['owningSide'] === true) {$def['localKey'] = true;} else if (($def['local'] !== $localIdColumnName && $def['type'] == Doctrine_Relation::ONE)) {$def['localKey'] = true;}} else if ($localIdentifierCount > 1 && ! isset($def['localKey'])) {// It's a composite key and since 'foreign' can not point to a composite// key currently, we know that 'local' must be the foreign key.$def['localKey'] = true;}}} else {if (isset($def['foreign'])) {$def['foreign'] = $def['table']->getColumnName($def['foreign']);// local key not set, but foreign key is set// try to guess the local keyif ($def['foreign'] === $foreignIdColumnName) {$def['localKey'] = true;try {$def['local'] = $this->guessColumns($foreignClasses, $this->_table);} catch (Doctrine_Relation_Exception $e) {$def['local'] = $localIdColumnName;}} else {$def['local'] = $localIdColumnName;}} else {// neither local or foreign key is being set// try to guess both keys$conn = $this->_table->getConnection();// the following loops are needed for covering inheritanceforeach ($localClasses as $class) {$table = $conn->getTable($class);$identifierColumnNames = $table->getIdentifierColumnNames();$idColumnName = array_pop($identifierColumnNames);$column = strtolower($table->getComponentName()). '_' . $idColumnName;foreach ($foreignClasses as $class2) {$table2 = $conn->getTable($class2);if ($table2->hasColumn($column)) {$def['foreign'] = $column;$def['local'] = $idColumnName;return $def;}}}foreach ($foreignClasses as $class) {$table = $conn->getTable($class);$identifierColumnNames = $table->getIdentifierColumnNames();$idColumnName = array_pop($identifierColumnNames);$column = strtolower($table->getComponentName()). '_' . $idColumnName;foreach ($localClasses as $class2) {$table2 = $conn->getTable($class2);if ($table2->hasColumn($column)) {$def['foreign'] = $idColumnName;$def['local'] = $column;$def['localKey'] = true;return $def;}}}// auto-add columns and auto-build relation$columns = array();foreach ((array) $this->_table->getIdentifierColumnNames() as $id) {// ?? should this not be $this->_table->getComponentName() ??$column = strtolower($table->getComponentName()). '_' . $id;$col = $this->_table->getColumnDefinition($id);$type = $col['type'];$length = $col['length'];unset($col['type']);unset($col['length']);unset($col['autoincrement']);unset($col['sequence']);unset($col['primary']);$def['table']->setColumn($column, $type, $length, $col);$columns[] = $column;}if (count($columns) > 1) {$def['foreign'] = $columns;} else {$def['foreign'] = $columns[0];}$def['local'] = $localIdColumnName;}}return $def;}}