Files
luos/thirdparty/propel-generator/classes/propel/phing/PropelCreoleTransformTask.php
Paula Quispe 9eb7d6cac2 HOR-2689
2017-08-03 17:00:30 -04:00

778 lines
21 KiB
PHP

<?php
/*
* $Id: PropelCreoleTransformTask.php 538 2007-01-10 14:36:03Z heltem $
*
* 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 please see
* <http://propel.phpdb.org>.
*/
include_once 'creole/Connection.php';
require_once 'phing/TaskPhing.php';
/**
* This class generates an XML schema of an existing database from
* Creole metadata.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Jason van Zyl <jvanzyl@periapt.com> (Torque)
* @author Fedor Karpelevitch <fedor.karpelevitch@barra.com> (Torque)
* @version $Revision: 538 $
* @package propel.phing
*/
class PropelCreoleTransformTask extends TaskPhing {
/** Name of XML database schema produced. */
protected $xmlSchema;
/** Creole URL. */
protected $dbUrl;
/** Creole driver. */
protected $dbDriver;
/** Creole user name. */
protected $dbUser;
/** Creole password. */
protected $dbPassword;
/** DB encoding to use */
protected $dbEncoding = 'iso-8859-1';
/** DB schema to use. */
protected $dbSchema;
/** parsed DB DSN */
protected $dsn;
/** DOM document produced. */
protected $doc;
/** The document root element. */
protected $databaseNode;
/** Hashtable of columns that have primary keys. */
protected $primaryKeys;
/** Hashtable to track what table a column belongs to. */
// doesn't seem to be used
// protected $columnTableMap;
/** whether to use same name for phpName or not */
protected $samePhpName;
/** whether to add vendor info or not */
protected $addVendorInfo;
/**
* Bitfield to switch on/off which validators will be created.
*
* @var int
*/
protected $validatorBits;
/**
* Collect validatorInfos to create validators.
*
* @var int
*/
protected $validatorInfos;
/**
* Zero bit for no validators
*/
const VALIDATORS_NONE = 0;
/**
* Bit for maxLength validator
*/
const VALIDATORS_MAXLENGTH = 1;
/**
* Bit for maxValue validator
*/
const VALIDATORS_MAXVALUE = 2;
/**
* Bit for type validator
*/
const VALIDATORS_TYPE = 4;
/**
* Bit for required validator
*/
const VALIDATORS_REQUIRED = 8;
/**
* Bit for unique validator
*/
const VALIDATORS_UNIQUE = 16;
/**
* Bit for all validators
*/
const VALIDATORS_ALL = 255;
/**
* Maps validator type tokens to bits
*
* The tokens are used in the propel.addValidators property to define
* which validators are to be added
*
* @static
* @var array
*/
static protected $validatorBitMap = array (
'none' => PropelCreoleTransformTask::VALIDATORS_NONE,
'maxlength' => PropelCreoleTransformTask::VALIDATORS_MAXLENGTH,
'maxvalue' => PropelCreoleTransformTask::VALIDATORS_MAXVALUE,
'type' => PropelCreoleTransformTask::VALIDATORS_TYPE,
'required' => PropelCreoleTransformTask::VALIDATORS_REQUIRED,
'unique' => PropelCreoleTransformTask::VALIDATORS_UNIQUE,
'all' => PropelCreoleTransformTask::VALIDATORS_ALL,
);
/**
* Defines messages that are added to validators
*
* @static
* @var array
*/
static protected $validatorMessages = array (
'maxlength' => array (
'msg' => 'The field %s must be not longer than %s characters.',
'var' => array('colName', 'value')
),
'maxvalue' => array (
'msg' => 'The field %s must be not greater than %s.',
'var' => array('colName', 'value')
),
'type' => array (
'msg' => 'The field %s is not a valid value.',
'var' => array('colName')
),
'required' => array (
'msg' => 'The field %s is required.',
'var' => array('colName')
),
'unique' => array (
'msg' => 'This %s already exists in table %s.',
'var' => array('colName', 'tableName')
),
);
public function getDbSchema()
{
return $this->dbSchema;
}
public function setDbSchema($dbSchema)
{
$this->dbSchema = $dbSchema;
}
public function setDbUrl($v)
{
$this->dbUrl = $v;
}
public function setDbDriver($v)
{
$this->dbDriver = $v;
}
public function setDbUser($v)
{
$this->dbUser = $v;
}
public function setDbPassword($v)
{
$this->dbPassword = $v;
}
public function setDbEncoding($v)
{
$this->dbEncoding = $v;
}
public function setOutputFile($v)
{
$this->xmlSchema = $v;
}
public function setSamePhpName($v)
{
$this->samePhpName = $v;
}
public function setAddVendorInfo($v)
{
$this->addVendorInfo = (boolean) $v;
}
/**
* Sets set validator bitfield from propel.addValidators property
*
* @param string $v The propel.addValidators property
* @return void
*/
public function setAddValidators($v)
{
// lowercase input
$v = strtolower($v);
// make it a bit expression
$v = str_replace(
array_keys(self::$validatorBitMap), self::$validatorBitMap, $v);
// check if it's a valid boolean expression
if (!preg_match('/[^\d|&~ ]/', $v)) {
// eval the expression
eval("\$v = $v;");
} else {
$this->log("\n\nERROR: NO VALIDATORS ADDED!\n\nThere is an error in propel.addValidators build property.\n\nAllowed tokens are: " . implode(', ', array_keys(self::$validatorBitMap)) . "\n\nAllowed operators are (like in php.ini):\n\n| bitwise OR\n& bitwise AND\n~ bitwise NOT\n\n", PROJECT_MSG_ERR);
$v = self::VALIDATORS_NONE;
}
$this->validatorBits = $v;
}
public function isSamePhpName()
{
return $this->samePhpName;
}
/**
* Default constructor.
* @return void
* @throws BuildException
*/
public function main()
{
$this->log("Propel - CreoleToXMLSchema starting");
$this->log("Your DB settings are:");
$this->log("driver : " . ($this->dbDriver ? $this->dbDriver : "(default)"));
$this->log("URL : " . $this->dbUrl);
//(not yet supported) $this->log("schema : " . $this->dbSchema);
//DocumentTypeImpl docType = new DocumentTypeImpl(null, "database", null,
// "http://jakarta.apache.org/turbine/dtd/database.dtd");
$this->doc = new DOMDocument('1.0', 'utf-8');
$this->doc->formatOutput = true; // pretty printing
$this->doc->appendChild($this->doc->createComment("Autogenerated by CreoleToXMLSchema!"));
try {
$this->generateXML();
$this->log("Writing XML to file: " . $this->xmlSchema);
$outFile = new PhingFile($this->xmlSchema);
$out = new FileWriter($outFile);
$xmlstr = $this->doc->saveXML();
$out->write($xmlstr);
$out->close();
} catch (Exception $e) {
$this->log("There was an error building XML from metadata: " . $e->getMessage(), PROJECT_MSG_ERR);
}
$this->log("Propel - CreoleToXMLSchema finished");
}
/**
* Generates an XML database schema from Creole metadata.
*
* @return void
* @throws Exception a generic exception.
*/
public function generateXML()
{
// Establish db connection
$con = $this->getConnection();
// Get the database Metadata.
$dbInfo = $con->getDatabaseInfo();
// create and add the database node
$databaseNode = $this->createDatabaseNode($dbInfo);
$this->doc->appendChild($databaseNode);
}
/**
* Establishes a Creole database connection
*
* @return object The connection
*/
protected function getConnection() {
// Attemtp to connect to a database.
$this->dsn = Creole::parseDSN($this->dbUrl);
if($this->dbUser) {
$this->dsn["username"] = $this->dbUser;
}
if ($this->dbPassword) {
$this->dsn["password"] = $this->dbPassword;
}
if ($this->dbDriver) {
Creole::registerDriver($this->dsn['phptype'], $this->dbDriver);
}
$con = Creole::getConnection($this->dsn);
$this->log("DB connection established");
return $con;
}
/**
* Creates a database node
*
* @param object $dbInfo The dbInfo for this db
* @return object The database node instance
*/
protected function createDatabaseNode($dbInfo) {
$this->log("Processing database");
$node = $this->doc->createElement("database");
$node->setAttribute("name", $dbInfo->getName());
if ($vendorNode = $this->createVendorInfoNode($dbInfo->getVendorSpecificInfo())) {
$node->appendChild($vendorNode);
}
// create and add table nodes
foreach($dbInfo->getTables() as $table) {
$tableNode = $this->createTableNode($table);
$node->appendChild($tableNode);
}
return $node;
}
/**
* Creates a table node
*
* @param object $table The table
* @return object The table node instance
*/
protected function createTableNode($table) {
$this->log("Processing table: " . $table->toString());
$node = $this->doc->createElement("table");
$node->setAttribute("name", $table->getName());
if ($this->isSamePhpName()) {
$node->setAttribute("phpName", $table->getName());
}
if ($vendorNode = $this->createVendorInfoNode($table->getVendorSpecificInfo())) {
$node->appendChild($vendorNode);
}
// Create and add column nodes, register column validators
$columns = $table->getColumns();
foreach($columns as $column) {
$columnNode = $this->createColumnNode($column);
$node->appendChild($columnNode);
$this->registerValidatorsForColumn($column);
if ($column->isAutoIncrement()) {
$idMethod = 'native';
}
}
if (isset($idMethod)) {
$node->setAttribute("idMethod", $idMethod);
}
// Create and add foreign key nodes.
$foreignKeys = $table->getForeignKeys();
foreach($foreignKeys as $foreignKey) {
$foreignKeyNode = $this->createForeignKeyNode($foreignKey);
$node->appendChild($foreignKeyNode);
}
// Create and add index nodes.
$indices = $table->getIndices();
foreach($indices as $index) {
$indexNode = $this->createIndexNode($index);
$node->appendChild($indexNode);
}
// add an id-method-parameter if we have a sequence that matches table_colname_seq
//
//
$pkey = $table->getPrimaryKey();
if ($pkey) {
$cols = $pkey->getColumns();
if (count($cols) === 1) {
$col = array_shift($cols);
if ($col->isAutoIncrement()) {
$seq_name = $table->getName().'_'.$col->getName().'_seq';
if ($table->getDatabase()->isSequence($seq_name)) {
$idMethodParameterNode = $this->doc->createElement("id-method-parameter");
$idMethodParameterNode->setAttribute("value", $seq_name);
$node->appendChild($idMethodParameterNode);
}
}
}
}
// Create and add validator and rule nodes.
$nodes = array();
$tableName = $table->getName();
if (isset($this->validatorInfos[$tableName])) {
foreach ($this->validatorInfos[$tableName] as $colName => $rules) {
$column = $table->getColumn($colName);
$colName = $column->getName();
foreach ($rules as $rule) {
if (!isset($nodes[$colName])) {
$nodes[$colName] = $this->createValidator($column, $rule['type']);
$node->appendChild($nodes[$colName]);
}
$ruleNode = $this->createRuleNode($column, $rule);
$nodes[$colName]->appendChild($ruleNode);
}
}
}
return $node;
}
/**
* Creates an column node
*
* @param object $column The Creole column
* @return object The column node instance
*/
protected function createColumnNode($column) {
$node = $this->doc->createElement("column");
$table = $column->getTable();
$colName = $column->getName();
$colType = $column->getType();
$colSize = $column->getSize();
$colScale = $column->getScale();
if ($colType === CreoleTypes::OTHER) {
$this->log("Column [" . $table->getName() . "." . $colName . "] has a column type (".$column->getNativeType().") that Propel does not support.", PROJECT_MSG_WARN);
}
$node->setAttribute("name", $colName);
if ($this->isSamePhpName()) {
$node->setAttribute("phpName", $colName);
}
$node->setAttribute("type", PropelTypes::getPropelType($colType));
if ($colSize > 0 && (
$colType == CreoleTypes::CHAR
|| $colType == CreoleTypes::VARCHAR
|| $colType == CreoleTypes::LONGVARCHAR
|| $colType == CreoleTypes::DECIMAL
|| $colType == CreoleTypes::FLOAT
|| $colType == CreoleTypes::NUMERIC)) {
$node->setAttribute("size", (string) $colSize);
}
if ($colScale > 0 && (
$colType == CreoleTypes::DECIMAL
|| $colType == CreoleTypes::FLOAT
|| $colType == CreoleTypes::NUMERIC)) {
$node->setAttribute("scale", (string) $colScale);
}
if (!$column->isNullable()) {
$node->setAttribute("required", "true");
}
if ($column->isAutoIncrement()) {
$node->setAttribute("autoIncrement", "true");
}
if (in_array($colName, $this->getTablePkCols($table))) {
$node->setAttribute("primaryKey", "true");
}
if (($defValue = $column->getDefaultValue()) !== null) {
$node->setAttribute("default", iconv($this->dbEncoding, 'utf-8', $defValue));
}
if ($vendorNode = $this->createVendorInfoNode($column->getVendorSpecificInfo())) {
$node->appendChild($vendorNode);
}
return $node;
}
/**
* Returns the primary key columns for a table
*
* @param object $table The table
* @return array The primary keys
*/
protected function getTablePkCols($table) {
static $columns = array();
$tableName = $table->getName();
if (!isset($columns[$tableName])) {
$columns[$tableName] = array();
$primaryKey = $table->getPrimaryKey();
if ($primaryKey) {
foreach($primaryKey->getColumns() as $colObject) {
$columns[$tableName][] = $colObject->getName();
}
}
}
return $columns[$tableName];
}
/**
* Creates an foreign key node
*
* @param object $foreignKey The foreign key
* @return object The foreign key node instance
*/
protected function createForeignKeyNode($foreignKey) {
$node = $this->doc->createElement("foreign-key");
if ($vendorNode = $this->createVendorInfoNode($foreignKey->getVendorSpecificInfo())) {
$node->appendChild($vendorNode);
}
$refs = $foreignKey->getReferences();
// all references must be to same table, so we can grab table from the first, foreign column
$node->setAttribute("foreignTable", $refs[0][1]->getTable()->getName());
$node->setAttribute("onDelete", $refs[0][2]);
$node->setAttribute("onUpdate", $refs[0][3]);
for($m = 0, $size = count($refs); $m < $size; $m++) {
$refNode = $this->doc->createElement("reference");
$refData = $refs[$m];
$refNode->setAttribute("local", $refData[0]->getName());
$refNode->setAttribute("foreign", $refData[1]->getName());
$node->appendChild($refNode);
}
return $node;
}
/**
* Creates an index node
*
* @param object $index The index
* @return object The index node instance
*/
protected function createIndexNode($index) {
$indexType = $index->isUnique() ? 'unique' : 'index';
$node = $this->doc->createElement($indexType);
$node->setAttribute("name", $index->getName());
$columns = $index->getColumns();
foreach ($columns as $column) {
$tableName = $column->getTable()->getName();
$colName = $column->getName();
$columnNode = $this->doc->createElement("{$indexType}-column");
$columnNode->setAttribute("name", $colName);
$node->appendChild($columnNode);
if ($indexType == 'unique' && $this->isValidatorRequired('unique')) {
$this->validatorInfos[$tableName][$colName][] = array('type' => 'unique');
}
}
if ($vendorNode = $this->createVendorInfoNode($index->getVendorSpecificInfo())) {
$node->appendChild($vendorNode);
}
return $node;
}
/**
* Checks whether to add validators of specified type or not
*
* @param string $type The validator type
* @return boolean
*/
protected function isValidatorRequired($type) {
$type = strtolower($type);
return ($this->validatorBits & self::$validatorBitMap[$type] ? true : false);
}
/**
* Registers column type specific validators if necessary
*
* We'll first collect the validators/rule infos and add them later on to
* have them appended to the table tag as a block.
*
* CreoleTypes are:
*
* BOOLEAN
* BIGINT, SMALLINT, TINYINT, INTEGER
* FLOAT, DOUBLE, NUMERIC, DECIMAL, REAL
* BIGINT, SMALLINT, TINYINT, INTEGER
* TEXT
* BLOB, CLOB, BINARY, VARBINARY, LONGVARBINARY
* DATE, YEAR, TIME
* TIMESTAMP
*
* We will add the following type specific validators:
*
* for notNull columns: required validator
* for unique indexes: unique validator
* for varchar types: maxLength validators (CHAR, VARCHAR, LONGVARCHAR)
* for numeric types: maxValue validators (BIGINT, SMALLINT, TINYINT, INTEGER, FLOAT, DOUBLE, NUMERIC, DECIMAL, REAL)
* for integer and timestamp types: notMatch validator with [^\d]+ (BIGINT, SMALLINT, TINYINT, INTEGER, TIMESTAMP)
* for float types: notMatch validator with [^\d\.]+ (FLOAT, DOUBLE, NUMERIC, DECIMAL, REAL)
*
* @param object $column The Creole column
* @return void
* @todo find out how to evaluate the appropriate size and adjust maxValue rule values appropriate
* @todo find out if float type column values must always notMatch('[^\d\.]+'), i.e. digits and point for any db vendor, language etc.
*/
protected function registerValidatorsForColumn($column) {
$table = $column->getTable();
$tableName = $table->getName();
$colName = $column->getName();
$colType = $column->getType();
$colSize = $column->getSize();
if ($this->isValidatorRequired('required')) {
$ruleInfo = array('type' => 'required');
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
$isPrimarykeyCol = in_array($colName, $this->getTablePkCols($table));
if ($this->isValidatorRequired('unique') && $isPrimarykeyCol) {
$ruleInfo = array('type' => 'unique');
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
if ($this->isValidatorRequired('maxLength') &&
$colSize > 0 && in_array($colType, array(
CreoleTypes::CHAR,
CreoleTypes::VARCHAR,
CreoleTypes::LONGVARCHAR))) {
$ruleInfo = array('type' => 'maxLength', 'value' => $colSize);
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
if ($this->isValidatorRequired('maxValue') &&
$colSize > 0 && in_array($colType, array(
CreoleTypes::SMALLINT,
CreoleTypes::TINYINT,
CreoleTypes::INTEGER,
CreoleTypes::BIGINT,
CreoleTypes::FLOAT,
CreoleTypes::DOUBLE,
CreoleTypes::NUMERIC,
CreoleTypes::DECIMAL,
CreoleTypes::REAL))) {
// TODO: how to evaluate the appropriate size??
$this->log("WARNING: maxValue validator added for column $colName. You will have to adjust the size value manually.", PROJECT_MSG_WARN);
$ruleInfo = array('type' => 'maxValue', 'value' => $colSize);
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
if ($this->isValidatorRequired('type') &&
$colSize > 0 && in_array($colType, array(
CreoleTypes::SMALLINT,
CreoleTypes::TINYINT,
CreoleTypes::INTEGER,
CreoleTypes::TIMESTAMP))) {
$ruleInfo = array('type' => 'type', 'value' => '[^\d]+');
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
if ($this->isValidatorRequired('type') &&
$colSize > 0 && in_array($colType, array(
CreoleTypes::FLOAT,
CreoleTypes::DOUBLE,
CreoleTypes::NUMERIC,
CreoleTypes::DECIMAL,
CreoleTypes::REAL))) {
// TODO: is this always true??
$ruleInfo = array('type' => 'type', 'value' => '[^\d\.]+');
$this->validatorInfos[$tableName][$colName][] = $ruleInfo;
}
}
/**
* Creates a validator node
*
* @param object $column The Creole column
* @param integer $type The validator type
* @return object The validator node instance
*/
protected function createValidator($column, $type) {
$node = $this->doc->createElement('validator');
$node->setAttribute('column', $column->getName());
return $node;
}
/**
* Creates a rule node
*
* @param object $column The Creole column
* @param array $rule The rule info
* @return object The rule node instance
*/
protected function createRuleNode($column, $rule) {
extract($rule);
// create message
$colName = $column->getName();
$tableName = $column->getTable()->getName();
$msg = self::$validatorMessages[strtolower($rule)];
array_unshift($tmp = compact($msg['var']), $msg['msg']);
$msg = call_user_func_array('sprintf', $tmp);
// add node
$node = $this->doc->createElement('rule');
$node->setAttribute('name', $rule == 'type' ? 'notMatch' : $rule);
$node->setAttribute('message', $msg);
return $node;
}
/**
* Creates a vendor info node
*
* returns false if no vendor info can or has to be added
*
* @param array $vendorInfo The validator info
* @return object|boolean The vendor info instance or false
*/
protected function createVendorInfoNode($vendorInfo)
{
if(!$vendorInfo OR !$this->addVendorInfo) {
return false;
}
$vendorNode = $this->doc->createElement("vendor");
$vendorNode->setAttribute("type", $this->dsn["phptype"]);
foreach($vendorInfo as $key => $value) {
$parameterNode = $this->doc->createElement("parameter");
$value = iconv($this->dbEncoding, "utf-8", $value);
$parameterNode->setAttribute("name", $key);
$parameterNode->setAttribute("value", $value);
$vendorNode->appendChild($parameterNode);
}
return $vendorNode;
}
}