. */ Doctrine::autoload('Doctrine_Access'); /** * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query. * Its purpose is to populate object graphs. * * * @package Doctrine ORM * @url www.phpdoctrine.com * @license LGPL */ abstract class Doctrine_Hydrate extends Doctrine_Access { /** * @var array $fetchmodes an array containing all fetchmodes */ protected $fetchModes = array(); /** * @var array $tables an array containing all the tables used in the query */ protected $tables = array(); /** * @var array $collections an array containing all collections this parser has created/will create */ protected $collections = array(); /** * @var array $joins an array containing all table joins */ protected $joins = array(); /** * @var array $data fetched data */ protected $data = array(); /** * @var Doctrine_Connection $connection Doctrine_Connection object */ protected $connection; /** * @var Doctrine_View $view Doctrine_View object */ protected $view; protected $inheritanceApplied = false; /** * @var boolean $aggregate */ protected $aggregate = false; /** * @var array $tableAliases */ protected $tableAliases = array(); /** * @var array $tableIndexes */ protected $tableIndexes = array(); /** * @var array $parts SQL query string parts */ protected $parts = array( "select" => array(), "from" => array(), "join" => array(), "where" => array(), "groupby" => array(), "having" => array(), "orderby" => array(), "limit" => false, "offset" => false, ); /** * constructor * * @param Doctrine_Connection|null $connection */ public function __construct($connection = null) { if( ! ($connection instanceof Doctrine_Connection)) $connection = Doctrine_Manager::getInstance()->getCurrentConnection(); $this->connection = $connection; } /** * getQuery * * @return string */ abstract public function getQuery(); /** * limitSubqueryUsed * * @return boolean */ public function isLimitSubqueryUsed() { return false; } /** * remove * * @param $name */ public function remove($name) { if(isset($this->parts[$name])) { if($name == "limit" || $name == "offset") $this->parts[$name] = false; else $this->parts[$name] = array(); } return $this; } /** * clear * resets all the variables * * @return void */ protected function clear() { $this->fetchModes = array(); $this->tables = array(); $this->parts = array( "select" => array(), "from" => array(), "join" => array(), "where" => array(), "groupby" => array(), "having" => array(), "orderby" => array(), "limit" => false, "offset" => false, ); $this->inheritanceApplied = false; $this->aggregate = false; $this->data = array(); $this->collections = array(); $this->joins = array(); $this->tableIndexes = array(); $this->tableAliases = array(); } /** * getConnection * * @return Doctrine_Connection */ public function getConnection() { return $this->connection; } /** * setView * sets a database view this query object uses * this method should only be called internally by doctrine * * @param Doctrine_View $view database view * @return void */ public function setView(Doctrine_View $view) { $this->view = $view; } /** * getView * * @return Doctrine_View */ public function getView() { return $this->view; } /** * getTableAlias * * @param string $path * @return string */ final public function getTableAlias($path) { if( ! isset($this->tableAliases[$path])) return false; return $this->tableAliases[$path]; } /** * getCollection * * @parma string $name component name * @param integer $index */ private function getCollection($name) { $table = $this->tables[$name]; switch($this->fetchModes[$name]): case Doctrine::FETCH_BATCH: $coll = new Doctrine_Collection_Batch($table); break; case Doctrine::FETCH_LAZY: $coll = new Doctrine_Collection_Lazy($table); break; case Doctrine::FETCH_OFFSET: $coll = new Doctrine_Collection_Offset($table); break; case Doctrine::FETCH_IMMEDIATE: $coll = new Doctrine_Collection_Immediate($table); break; case Doctrine::FETCH_LAZY_OFFSET: $coll = new Doctrine_Collection_LazyOffset($table); break; default: throw new Doctrine_Exception("Unknown fetchmode"); endswitch; $coll->populate($this); return $coll; } /** * getData * @param $key the component name * @return array the data row for the specified component */ final public function getData($key) { if(isset($this->data[$key]) && is_array($this->data[$key])) return $this->data[$key]; return array(); } /** * convertBoolean * converts boolean to integers * * @param mixed $item * @return void */ public static function convertBoolean(&$item) { if(is_bool($item)) $item = (int) $item; } /** * execute * executes the dql query and populates all collections * * @param string $params * @return Doctrine_Collection the root collection */ public function execute($params = array(), $return = Doctrine::FETCH_RECORD) { $this->data = array(); $this->collections = array(); array_walk($params, array(__CLASS__, 'convertBoolean')); if( ! $this->view) $query = $this->getQuery(); else $query = $this->view->getSelectSql(); if($this->isLimitSubqueryUsed()) $params = array_merge($params, $params); $stmt = $this->connection->execute($query,$params); if($this->aggregate) return $stmt->fetchAll(PDO::FETCH_ASSOC); switch(count($this->tables)): case 0: throw new Doctrine_Exception("No tables selected"); break; case 1: $keys = array_keys($this->tables); $name = $this->tables[$keys[0]]->getComponentName(); while($data = $stmt->fetch(PDO::FETCH_ASSOC)): foreach($data as $key => $value): $e = explode("__",$key); if(count($e) > 1) { $data[end($e)] = $value; } else { $data[$e[0]] = $value; } unset($data[$key]); endforeach; $this->data[$name][] = $data; endwhile; return $this->getCollection($keys[0]); break; default: $keys = array_keys($this->tables); $root = $keys[0]; $previd = array(); $coll = $this->getCollection($root); $prev[$root] = $coll; if($this->aggregate) $return = Doctrine::FETCH_ARRAY; $array = $this->parseData($stmt); if($return == Doctrine::FETCH_VHOLDER) { return $this->hydrateHolders($array); } elseif($return == Doctrine::FETCH_ARRAY) return $array; foreach($array as $data) { /** * remove duplicated data rows and map data into objects */ foreach($data as $key => $row) { if(empty($row)) continue; $ids = $this->tables[$key]->getIdentifier(); $name = $key; if($this->isIdentifiable($row, $ids)) { $pointer = $this->joins[$name]; $path = array_search($name, $this->tableAliases); $tmp = explode(".", $path); $alias = end($tmp); unset($tmp); $fk = $this->tables[$pointer]->getRelation($alias); if( ! isset($prev[$pointer]) ) continue; $last = $prev[$pointer]->getLast(); if( ! $fk->isOneToOne()) { if($last instanceof Doctrine_Record) { if( ! $last->hasReference($alias)) { $prev[$name] = $this->getCollection($name); $last->initReference($prev[$name],$fk); } } } continue; } if( ! isset($previd[$name])) $previd[$name] = array(); if($previd[$name] !== $row) { // set internal data $this->tables[$name]->setData($row); // initialize a new record $record = $this->tables[$name]->getRecord(); if($name == $root) { // add record into root collection $coll->add($record); unset($previd); } else { $pointer = $this->joins[$name]; $path = array_search($name, $this->tableAliases); $tmp = explode(".", $path); $alias = end($tmp); unset($tmp); $fk = $this->tables[$pointer]->getRelation($alias); $last = $prev[$pointer]->getLast(); if($fk->isOneToOne()) { // one-to-one relation if($fk instanceof Doctrine_LocalKey) $last->set($fk->getLocal(), $record->getIncremented(), false); $last->set($fk->getAlias(), $record); $prev[$name] = $record; } else { // one-to-many relation or many-to-many relation if( ! $last->hasReference($alias)) { $prev[$name] = $this->getCollection($name); $last->initReference($prev[$name], $fk); //$last->set($fk->getAlias(), $this->getCollection($name)); } else { // previous entry found from identityMap $prev[$name] = $last->get($alias); } $last->addReference($record, $fk); } } } $previd[$name] = $row; } } return $coll; endswitch; } /** * hydrateHolders * * @param array $array */ public function hydrateHolders(array $array) { $keys = array_keys($this->tables); $root = $keys[0]; $coll = new Doctrine_ValueHolder($this->tables[$root]); foreach($keys as $key) { $prev[$key] = array(); } foreach($array as $data) { foreach($data as $alias => $row) { if(isset($prev[$alias]) && $row !== $prev[$alias]) { $holder = new Doctrine_ValueHolder($this->tables[$alias]); $holder->data = $row; if($alias === $root) { $coll->data[] = $holder; } else { $pointer = $this->joins[$alias]; $component = $this->tables[$alias]->getComponentName(); $last[$pointer]->data[$component][] = $holder; } $last[$alias] = $holder; } $prev[$alias] = $row; } } return $coll; } /** * isIdentifiable * returns whether or not a given data row is identifiable (it contains * all id fields specified in the second argument) * * @param array $row * @param mixed $ids * @return boolean */ public function isIdentifiable(array $row, $ids) { $emptyID = false; if(is_array($ids)) { foreach($ids as $id) { if($row[$id] == null) return true; } } else { if( ! isset($row[$ids])) return true; } return false; } /** * applyInheritance * applies column aggregation inheritance to DQL / SQL query * * @return string */ public function applyInheritance() { // get the inheritance maps $array = array(); foreach($this->tables as $alias => $table): $array[$alias][] = $table->getInheritanceMap(); endforeach; // apply inheritance maps $str = ""; $c = array(); $index = 0; foreach($array as $tname => $maps) { $a = array(); foreach($maps as $map) { $b = array(); foreach($map as $field => $value) { if($index > 0) $b[] = "(".$tname.".$field = $value OR $tname.$field IS NULL)"; else $b[] = $tname.".$field = $value"; } if( ! empty($b)) $a[] = implode(" AND ",$b); } if( ! empty($a)) $c[] = implode(" AND ",$a); $index++; } $str .= implode(" AND ",$c); return $str; } /** * parseData * parses the data returned by PDOStatement * * @return array */ public function parseData(PDOStatement $stmt) { $array = array(); while($data = $stmt->fetch(PDO::FETCH_ASSOC)): /** * parse the data into two-dimensional array */ foreach($data as $key => $value): $e = explode("__",$key); $field = strtolower( array_pop($e) ); $component = strtolower( implode("__",$e) ); $data[$component][$field] = $value; unset($data[$key]); endforeach; $array[] = $data; endwhile; $stmt->closeCursor(); return $array; } /** * returns a Doctrine_Table for given name * * @param string $name component name * @return Doctrine_Table */ public function getTable($name) { return $this->tables[$name]; } }