* @package Mammut\DB\Model */ class TableInfo extends \Mammut\StrictObject implements XMLModel { /** * * @var string */ private $name = ""; /** * * @var array */ private $columns = array(); /** * * @var boolean */ private $transactionSave = false; /** * * @var array */ private $primary = array(); /** * * @var array */ private $indexes = array(); /** * * @var array references (foreign keys) */ private $ref = array(); /** * Value checks * @var PredicateSet[] */ private $checks = array(); public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getColumnCount() { return count($this->columns); } /** * adds a new column to the table info object * * @param ColumnInfo $info * column info object which describes the column * @param mixed $before * the index where the object should be inserted, or -1 to place it on the * end (optional) */ public function addColumn(ColumnInfo $info, $before = -1) { foreach($this->columns as $c) { if($info->getName() == $c->getName()) throw new \InvalidArgumentException("duplicate column name: " . $c->getName()); } if($before == -1) $this->columns[] = $info; else { $new = array(); for($i = 0; $i < $before; $i++) { $new[] = $this->columns[$i]; } $new[] = $info; for($i = $before, $max = count($this->columns); $i < $max; $i++) { $new[] = $this->columns[$i]; } $this->columns = $new; } } public function columnExists($name) { return $this->getColumnInfo($name) !== false; } /** * Fetches a info object of a specific column. * * Returns a {@link ColumnInfo} object for the column $col, which can be specified by its name or its index, * starting with 0. * * @param mixed $col * the column name or index * @return ColumnInfo */ public function getColumnInfo($col) { // echo "seaching colinfo for $col\n"; if(!is_int($col)) { $c = count($this->columns); for($i = 0; $i < $c; $i++) { if($this->columns[$i]->getName() == $col) return $this->columns[$i]; } return false; } if($col < 0) throw new \InvalidArgumentException("invalid column index"); return $this->columns[(int) $col]; } /** * Lookup the index of a given column name. * * Converts a column name to its matching index number. * * @param $name string * the name of the column * @return int the column number or -1 if none was found with the name given */ public function getColumnNum($name) { $c = count($this->columns); for($i = 0; $i < $c; $i++) { if($this->columns[$i]->getName() == $name) return $i; } return -1; } /** * Removes a column from the model. * * Removes the specified column from the model (NOT from the database object). * * @param mixed $id * the column name or index */ public function delColumn($id) { if(is_int($id)) { $new = array(); for($i = 0; $i < $id; $i++) $new[] = $this->columns[$i]; for($i = $id + 1; $i < count($this->columns); $i++) $new[] = $this->columns[$i]; unset($this->columns); $this->columns = $new; } else { $i = 0; while(isset($this->columns[$i]) && ($this->columns[$i]->getName() != $id)) $i++; if(isset($this->columns[$i])) $this->delColumn((int) $i); } } /** * Set the primary key fields. * * @param array $primaryFields */ public function setPrimary(array $primaryFields) { $this->primary = $primaryFields; } /** * Fetches the primary key fields. * * @return array the fields which are the primary key of the table */ public function getPrimary() { return $this->primary; } /** * Defines a index. * * @param string $name * the index name * @param array $columns * the columuns which should be added to the index */ public function setIndex($name, array $columns) { $this->indexes[(string) $name] = $columns; } /** * Removes a index. * * @param string $name * the index name */ public function removeIndex($name) { unset($this->indexes[$name]); } /** * Returns a list of all defined indexes. * * @return array the index list */ public function getIndexes() { return $this->indexes; } /** * Adds a table reference. * * Adds a table reference to the model. This will be realized as a foreign key constraint if * supported by the RDBS. * * @param string $name the column * @param mixed $targetObject * @param string $targetColumn * @param string $update a RefInfo::ACT_* constant * @param string $delete a RefInfo::ACT_* constant * @see RefInfo */ public function setRef($name, $targetObject, $targetColumn = '', $update = RefInfo::ACT_PREVENT, $delete = RefInfo::ACT_PREVENT) { if ($targetObject instanceof RefInfo) { $targetObject->setBase(name); $this->ref[(string) $name] = $targetObject; } else $this->ref[(string) $name] = new RefInfo($name, $targetObject, $targetColumn, $update, $delete); } public function getRef($name) { return isset($this->ref[$name]) ? $this->ref[$name] : NULL; } public function removeRef($name) { unset($this->ref[$name]); } public function getRefs() { return $this->ref; } public function addCheck($name, PredicateSet $check) { $this->checks[$name] = $check; } public function getChecks() { return $this->checks; } public function clearChecks() { $this->checks = array(); } /** * defines the table as an transaction save one. * setting this * to true automatically disables voltaile settings * * @param boolean $isSave */ public function setTransactionSave($isSave) { $this->transactionSave = (bool) $isSave; } public function isTransactionSave() { return $this->transactionSave; } public function compareTo(TableInfo $otherInfo) { $colMismatch = array(); $toremove = array(); $missing = array(); for($i = 0; $i < $this->getColumnCount(); $i++) { $c1 = $this->getColumnInfo($i); $found = false; for($j = 0; $j < $otherInfo->getColumnCount(); $j++) { $c2 = $otherInfo->getColumnInfo($j); if($c1->getName() == $c2->getName()) { $mismatch = array(); if($c1->getType() != $c2->getType()) { $equal = false; $mismatch['type'] = array( 'this' => $c1->getType(),'other' => $c2->getType()); } elseif($c1->getType() == ColumnInfo::TYPE_ENUM) { foreach($c1->getParam() as $e) { if(preg_match('#values\(([a-zA-Z0-9_,]*)\)#', $e, $match)) { $values1 = explode(',', $match[1]); break; } } foreach($c2->getParam() as $e) { if(preg_match('#values\(([a-zA-Z0-9_,]*)\)#', $e, $match)) { $values2 = explode(',', $match[1]); break; } } if(count(array_diff($values1, $values2)) != 0) { $equal = false; $mismatch['enum'] = array('this' => $values1,'other' => $values2); } } if($c1->getSize() != $c2->getSize() && ($c1->getType() == ColumnInfo::TYPE_CHAR || $c1->getType() == ColumnInfo::TYPE_VCHAR || $c1->getType() == ColumnInfo::TYPE_BINARY)) { $equal = false; $mismatch['size'] = array( 'this' => $c1->getSize(),'other' => $c2->getSize()); } $p1 = $c1->getParam(); $p2 = $c2->getParam(); if (in_array(ColumnInfo::P_ALLOW_NULL, $p1) != in_array(ColumnInfo::P_ALLOW_NULL, $p2)) $mismatch['null'] = [ 'this' => in_array(ColumnInfo::P_ALLOW_NULL, $p1),'other' => in_array(ColumnInfo::P_ALLOW_NULL, $p2)]; $found = true; if(count($mismatch) > 0) $colMismatch[$c1->getName()] = $mismatch; } } if(!$found) { array_push($missing, $c1->getName()); $equal = false; } } for($i = 0; $i < $otherInfo->getColumnCount(); $i++) { $c1 = $otherInfo->getColumnInfo($i); $found = false; for($j = 0; $j < $this->getColumnCount(); $j++) { $c2 = $this->getColumnInfo($j); if($c1->getName() == $c2->getName()) $found = true; } if(!$found) { array_push($toremove, $c1->getName()); $equal = false; } } $result = new \stdClass(); if (!array_compare($this->getPrimary(), $otherInfo->getPrimary())) $result->primary = ['this' => $this->getPrimary(), 'other' => $otherInfo->getPrimary()]; if(count($missing) > 0) $result->missingColumns = $missing; if(count($colMismatch) > 0) $result->differentColumns = $colMismatch; if(count($toremove) > 0) $result->additionalColumns = $toremove; return $result; } public static function fromXML($data) { if($data instanceof \SimpleXMLElement) $xml = $data; elseif ($data instanceof \Mammut\IO\iOutput) { $st = ''; $data->isSeekSupported() && $data->seek(0); while (!$data->isEOF()) $st .= $data->read(65535); $xml = simplexml_load_string((string) $st); } else $xml = simplexml_load_string((string) $data); if ($xml->getName() == 'tables') { $result = array(); foreach ($xml->table as $table) { $tbl = self::fromXML($table); $result[] = $tbl; } return $result; } $result = new TableInfo((string) $xml['name']); if(isset($xml->primary)) $result->setPrimary(explode(',', (string) $xml->primary)); if(isset($xml['transaction'])) $result->setTransactionSave($xml['transaction'] == 'true' ? true : false); else $result->setTransactionSave(false); foreach($xml->column as $c) { $newCol = ColumnInfo::fromXML($c->asXML()); $result->addColumn($newCol); } if(count($xml->index) > 0) { foreach($xml->index as $i) $result->setIndex((string) $i['name'], explode(',', (string) $i)); } foreach($xml->ref as $r) { $update = RefInfo::ACT_CASCADE; $delete = RefInfo::ACT_CASCADE; if (isset($r['update'])) { switch (strtolower($r['update'])) { case 'refuse': $update = RefInfo::ACT_PREVENT; break; case 'cascade': $update = RefInfo::ACT_CASCADE; break; case 'null': $update = RefInfo::ACT_NULL; break; } } if (isset($r['delete'])) { switch (strtolower($r['delete'])) { case 'refuse': $delete = RefInfo::ACT_PREVENT; break; case 'cascade': $delete = RefInfo::ACT_CASCADE; break; case 'null': $delete = RefInfo::ACT_NULL; break; } } $result->setRef((string) $r['name'], (string) $r['table'], (string) $r['attrib'], $update, $delete); } if(count($xml->check) > 0) { // foreach($xml->check as $c) { // $check = new Condition(); // foreach($c->validate as $v) { // } // $result->addCheck($check); // } } return $result; } public function toXML() { $xml = 'name . '"'; $xml .= ' transaction="' . ($this->transactionSave ? 'true' : 'false') . '"'; $xml .= '>'; $xml .= '' . implode(',', $this->primary) . ''; foreach($this->indexes as $name=>$fields) $xml .= '' . implode(',', $fields) . ''; foreach($this->columns as $c) $xml .= $c->toXML(); $xml .= ''; return $xml; } }