drupal-civicrm/sites/all/modules/civicrm/packages/DB/Table/Generator.php
2018-01-14 13:10:16 +00:00

1333 lines
44 KiB
PHP

<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* DB_Table_Generator - Generates DB_Table subclass skeleton code
*
* Parts of this class were adopted from the DB_DataObject PEAR package.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 1997-2007, Paul M. Jones <pmjones@php.net>
* Alan Knowles <alan@akbkhome.com>
* David C. Morse <morse@php.net>
* Mark Wiesemann <wiesemann@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The names of the authors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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.
*
* @category Database
* @package DB_Table
* @author Alan Knowles <alan@akbkhome.com>
* @author David C. Morse <morse@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Generator.php,v 1.17 2008/05/14 18:36:27 wiesemann Exp $
* @link http://pear.php.net/package/DB_Table
*/
// {{{ Includes
/**#@+
* Include basic classes
*/
/**
* The PEAR class (used for errors)
*/
require_once 'PEAR.php';
/**
* DB_Table table abstraction class
*/
require_once 'DB/Table.php';
/**
* DB_Table_Manager class (used to reverse engineer indices)
*/
require_once 'DB/Table/Manager.php';
/**#@-*/
// }}}
// {{{ Error code constants
/**#@+
* Error codes
*/
/**
* Parameter is not a DB/MDB2 object
*/
define('DB_TABLE_GENERATOR_ERR_DB_OBJECT', -301);
/**
* Parameter is not a DB/MDB2 object
*/
define('DB_TABLE_GENERATOR_ERR_INDEX_COL', -302);
/**
* Error while creating file/directory
*/
define('DB_TABLE_GENERATOR_ERR_FILE', -303);
/**#@-*/
// }}}
// {{{ Error messages
/**
* US-English default error messages.
*/
$GLOBALS['_DB_TABLE_GENERATOR']['default_error'] = array(
DB_TABLE_GENERATOR_ERR_DB_OBJECT =>
'Invalid DB/MDB2 object parameter. Function',
DB_TABLE_GENERATOR_ERR_INDEX_COL =>
'Index column is not a valid column name. Index column',
DB_TABLE_GENERATOR_ERR_FILE =>
'Can\'t create file/directory:'
);
// merge default and user-defined error messages
if (!isset($GLOBALS['_DB_TABLE_GENERATOR']['error'])) {
$GLOBALS['_DB_TABLE_GENERATOR']['error'] = array();
}
foreach ($GLOBALS['_DB_TABLE_GENERATOR']['default_error'] as $code => $message) {
if (!array_key_exists($code, $GLOBALS['_DB_TABLE_GENERATOR']['error'])) {
$GLOBALS['_DB_TABLE_GENERATOR']['error'][$code] = $message;
}
}
// }}}
// {{{ class DB_Table_Generator
/**
* class DB_Table_Generator - Generates DB_Table subclass skeleton code
*
* This class generates the php code necessary to use the DB_Table
* package to interact with an existing database. This requires the
* generation of a skeleton subclass definition be generated for each
* table in the database, in which the $col, $idx, and $auto_inc_col
* properties are constructed using a table schema that is obtained
* by querying the database.
*
* The class can also generate a file, named 'Database.php' by default,
* that includes (require_once) each of the table subclass definitions,
* instantiates one object of each DB_Table subclass (i.e., one object
* for each table), instantiates a parent DB_Table_Database object,
* adds all the tables to that parent, attempts to guess foreign key
* relationships between tables based on the column names, and adds
* the inferred references to the parent object.
*
* All of the code is written to a directory whose path is given by
* the property $class_write_path. By default, this is the current
* directory. By default, the name of the class constructed for a
* table named 'thing' is "Thing_Table". That is, the class name is
* the table name, with the first letter upper case, with a suffix
* '_Table'. This suffix can be changed by setting the $class_suffix
* property. The file containing a subclass definition is the
* subclass name with a php extension, e.g., 'Thing_Table.php'. The
* object instantiated from that subclass is the same as the table
* name, with no suffix, e.g., 'thing'.
*
* To generate the code for all of the tables in a database named
* $database, instantiate a MDB2 or DB object named $db that connects
* to the database of interest, and execute the following code:
* <code>
* $generator = new DB_Table_Generator($db, $database);
* $generator->class_write_path = $class_write_path;
* $generator->generateTableClassFiles();
* $generator->generateDatabaseFile();
* </code>
* Here $class_write_path should be the path (without a trailing
* separator) to a directory in which all of the code should be
* written. If this directory does not exist, it will be created.
* If the directory does already exist, exising files will not
* be overwritten. If $class_write_path is not set (i.e., if this
* line is omitted) all the code will be written to the current
* directory. If ->generateDatabaseFile() is called, it must be
* called after ->generateTableClassFiles().
*
* By default, ->generateTableClassFiles() and ->generateDatabaseFiles()
* generate code for all of the tables in the current database. To
* generate code for a specified list of tables, set the value of the
* public $tables property to a sequential list of table names before
* calling either of these methods. Code can be generated for three
* tables named 'table1', 'table2', and 'table3' as follows:
* <code>
* $generator = new DB_Table_Generator($db, $database);
* $generator->class_write_path = $class_write_path;
* $generator->tables = array('table1', 'table2', 'table3');
* $generator->generateTableClassFiles();
* $generator->generateDatabaseFile();
* </code>
* If the $tables property is not set to a non-null value prior
* to calling ->generateTableClassFiles() then, by default, the
* database is queried for a list of all table names, by calling the
* ->getTableNames() method from within ->generateTableClassFiles().
*
* PHP version 4 and 5
*
* @category Database
* @package DB_Table
* @author Alan Knowles <alan@akbkhome.com>
* @author David C. Morse <morse@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version Release: 1.5.6
* @link http://pear.php.net/package/DB_Table
*/
class DB_Table_Generator
{
// {{{ Properties
/**
* Name of the database
*
* @var string
* @access public
*/
var $name = null;
/**
* The PEAR DB/MDB2 object that connects to the database.
*
* @var object
* @access private
*/
var $db = null;
/**
* The backend type. May have values 'db' or 'mdb2'
*
* @var string
* @access private
*/
var $backend = null;
/**
* If there is an error on instantiation, this captures that error.
*
* This property is used only for errors encountered in the constructor
* at instantiation time. To check if there was an instantiation error...
*
* <code>
* $obj =& new DB_Table_Generator();
* if ($obj->error) {
* // ... error handling code here ...
* }
* </code>
*
* @var object PEAR_Error
* @access public
*/
var $error = null;
/**
* Numerical array of table name strings
*
* @var array
* @access public
*/
var $tables = array();
/**
* Class being extended (DB_Table or generic subclass)
*
* @var string
* @access public
*/
var $extends = 'DB_Table';
/**
* Path to definition of the class $this->extends
*
* @var string
* @access public
*/
var $extends_file = 'DB/Table.php';
/**
* Suffix to add to table names to obtain corresponding class names
*
* @var string
* @access public
*/
var $class_suffix = "_Table";
/**
* Path to directory in which subclass definitions should be written
*
* Value should not include a trailing "/".
*
* @var string
* @access public
*/
var $class_write_path = '';
/**
* Include path to subclass definition files from database file
*
* Used to create require_once statements in the Database.php file,
* which is in the same directory as the class definition files. Leave
* as empty string if your PHP include_path contains ".". The value
* should not include a trailing "/", which is added automatically
* to values other than the empty string.
*
* @var string
* @access public
*/
var $class_include_path = '';
/**
* Array of column definitions
*
* Array $this->col[table_name][column_name] = column definition.
* Column definition is an array with the same format as the $col
* property of a DB_Table object
*
* @var array
* @access public
*/
var $col = array();
/**
* Array of index/constraint definitions.
*
* Array $this->idx[table_table][index_name] = Index definition.
* The index definition is an array with the same format as the
* DB_Table $idx property property array.
*
* @var array
* @access public
*/
var $idx = array();
/**
* Array of auto_increment column names
*
* Array $this->auto_inc_col[table_name] = auto-increment column
*
* @var array
* @access public
*/
var $auto_inc_col = array();
/**
* Array of primary keys
*
* @var array
* @access public
*/
var $primary_key = array();
/**
* MDB2 'idxname_format' option, format of index names
*
* For use in printf() formatting. Use '%s' to use index names as
* returned by getTableConstraints/Indexes, and '%s_idx' to add an
* '_idx' suffix. For MySQL, use the default value '%'.
*
* @var string
* @access public
*/
var $idxname_format = '%s';
// }}}
// {{{ function DB_Table_Generator(&$db, $name)
/**
* Constructor
*
* If an error is encountered during instantiation, the error
* message is stored in the $this->error property of the resulting
* object. See $error property docblock for a discussion of error
* handling.
*
* @param object &$db DB/MDB2 database connection object
* @param string $name database name string
*
* @return object DB_Table_Generator
* @access public
*/
function __construct(&$db, $name)
{
// Is $db an DB/MDB2 object or null?
if (is_a($db, 'db_common')) {
$this->backend = 'db';
} elseif (is_a($db, 'mdb2_driver_common')) {
$this->backend = 'mdb2';
} else {
$this->error =&
DB_Table_Generator::throwError(DB_TABLE_GENERATOR_ERR_DB_OBJECT,
'DB_Table_Generator');
return;
}
$this->db =& $db;
$this->name = $name;
}
// }}}
// {{{ function &throwError($code, $extra = null)
/**
* Specialized version of throwError() modeled on PEAR_Error.
*
* Throws a PEAR_Error with a DB_Table_Generator error message based
* on a DB_Table_Generator constant error code.
*
* @param string $code A DB_Table_Generator error code constant.
* @param string $extra Extra text for the error (in addition to the
* regular error message).
*
* @return object PEAR_Error
* @access public
* @static
*/
function &throwError($code, $extra = null)
{
// get the error message text based on the error code
$text = 'DB_TABLE_GENERATOR ERROR - ' . "\n"
. $GLOBALS['_DB_TABLE_GENERATOR']['error'][$code];
// add any additional error text
if ($extra) {
$text .= ' ' . $extra;
}
// done!
$error = PEAR::throwError($text, $code);
return $error;
}
// }}}
// {{{ function setErrorMessage($code, $message = null)
/**
* Overwrites one or more error messages, e.g., to internationalize them.
*
* @param mixed $code If string, the error message with code $code will be
* overwritten by $message. If array, each key is a
* code and each value is a new message.
* @param string $message Only used if $key is not an array.
*
* @return void
* @access public
*/
function setErrorMessage($code, $message = null)
{
if (is_array($code)) {
foreach ($code as $single_code => $single_message) {
$GLOBALS['_DB_TABLE_GENERATOR']['error'][$single_code]
= $single_message;
}
} else {
$GLOBALS['_DB_TABLE_GENERATOR']['error'][$code] = $message;
}
}
// }}}
// {{{ function getTableNames()
/**
* Gets a list of tables from the database
*
* Upon successful completion, names are stored in the $this->tables
* array. If an error is encountered, a PEAR Error is returned, and
* $this->tables is reset to null.
*
* @return mixed true on success, PEAR Error on failure
* @access public
*/
function getTableNames()
{
if ($this->backend == 'db') {
// try getting a list of schema tables first. (postgres)
$this->db->expectError(DB_ERROR_UNSUPPORTED);
$this->tables = $this->db->getListOf('schema.tables');
$this->db->popExpect();
if (PEAR::isError($this->tables)) {
// try a list of tables, not qualified by 'schema'
$this->db->expectError(DB_ERROR_UNSUPPORTED);
$this->tables = $this->db->getListOf('tables');
$this->db->popExpect();
}
} else {
// Temporarily change 'portability' MDB2 option
$portability = $this->db->getOption('portability');
$this->db->setOption('portability',
MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
$this->db->loadModule('Manager');
$this->db->loadModule('Reverse');
// Get list of tables
$this->tables = $this->db->manager->listTables();
// Restore original MDB2 'portability'
$this->db->setOption('portability', $portability);
}
if (PEAR::isError($this->tables)) {
$error = $this->tables;
$this->tables = null;
return $error;
} else {
$this->tables = array_map(array($this, 'tableName'),
$this->tables);
return true;
}
}
// }}}
// {{{ function getTableDefinition($table)
/**
* Gets column and index definitions by querying database
*
* Upon return, column definitions are stored in $this->col[$table],
* and index definitions in $this->idx[$table].
*
* Calls DB/MDB2::tableInfo() for column definitions, and uses
* the DB_Table_Manager class to obtain index definitions.
*
* @param string $table name of table
*
* @return mixed true on success, PEAR Error on failure
* @access public
*/
function getTableDefinition($table)
{
/*
// postgres strip the schema bit from the
if (!empty($options['generator_strip_schema'])) {
$bits = explode('.', $table,2);
$table = $bits[0];
if (count($bits) > 1) {
$table = $bits[1];
}
}
*/
if ($this->backend == 'db') {
$defs = $this->db->tableInfo($table);
if (PEAR::isError($defs)) {
return $defs;
}
$this->columns[$table] = $defs;
} else {
// Temporarily change 'portability' MDB2 option
$portability = $this->db->getOption('portability');
$this->db->setOption('portability',
MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
$this->db->loadModule('Manager');
$this->db->loadModule('Reverse');
// Columns
$defs = $this->db->reverse->tableInfo($table);
if (PEAR::isError($defs)) {
return $defs;
}
// rename the 'length' key, so it matches db's return.
foreach ($defs as $k => $v) {
if (isset($defs[$k]['length'])) {
$defs[$k]['len'] = $defs[$k]['length'];
}
}
$this->columns[$table] = $defs;
// Temporarily set 'idxname_format' MDB2 option to $this->idx_format
$idxname_format = $this->db->getOption('idxname_format');
$this->db->setOption('idxname_format', $this->idxname_format);
}
// Default - no auto increment column
$this->auto_inc_col[$table] = null;
// Loop over columns to create $this->col[$table]
$this->col[$table] = array();
foreach ($defs as $t) {
$name = $t['name'];
$col = array();
switch (strtoupper($t['type'])) {
case 'INT2': // postgres
case 'TINYINT':
case 'TINY': //mysql
case 'SMALLINT':
$col['type'] = 'smallint';
break;
case 'INT4': // postgres
case 'SERIAL4': // postgres
case 'INT':
case 'SHORT': // mysql
case 'INTEGER':
case 'MEDIUMINT':
case 'YEAR':
$col['type'] = 'integer';
break;
case 'BIGINT':
case 'LONG': // mysql
case 'INT8': // postgres
case 'SERIAL8': // postgres
$col['type'] = 'bigint';
break;
case 'REAL':
case 'NUMERIC':
case 'NUMBER': // oci8
case 'FLOAT': // mysql
case 'FLOAT4': // real (postgres)
$col['type'] = 'single';
break;
case 'DOUBLE':
case 'DOUBLE PRECISION': // double precision (firebird)
case 'FLOAT8': // double precision (postgres)
$col['type'] = 'double';
break;
case 'DECIMAL':
case 'MONEY': // mssql and maybe others
$col['type'] = 'decimal';
break;
case 'BIT':
case 'BOOL':
case 'BOOLEAN':
$col['type'] = 'boolean';
break;
case 'STRING':
case 'CHAR':
$col['type'] = 'char';
break;
case 'VARCHAR':
case 'VARCHAR2':
case 'TINYTEXT':
$col['type'] = 'varchar';
break;
case 'TEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
$col['type'] = 'clob';
break;
case 'DATE':
$col['type'] = 'date';
break;
case 'TIME':
$col['type'] = 'time';
break;
case 'DATETIME': // mysql
case 'TIMESTAMP':
$col['type'] = 'timestamp';
break;
case 'ENUM':
case 'SET': // not really but oh well
case 'TIMESTAMPTZ': // postgres
case 'BPCHAR': // postgres
case 'INTERVAL': // postgres (eg. '12 days')
case 'CIDR': // postgres IP net spec
case 'INET': // postgres IP
case 'MACADDR': // postgress network Mac address.
case 'INTEGER[]': // postgres type
case 'BOOLEAN[]': // postgres type
$col['type'] = 'varchar';
break;
default:
$col['type'] = $t['type'] . ' (Unknown type)';
break;
}
// Set length and scope if required
if (in_array($col['type'], array('char','varchar','decimal'))) {
if (isset($t['len'])) {
$col['size'] = (int) $t['len'];
} elseif ($col['type'] == 'varchar') {
$col['size'] = 255; // default length
} elseif ($col['type'] == 'char') {
$col['size'] = 128; // default length
} elseif ($col['type'] == 'decimal') {
$col['size'] = 15; // default length
}
if ($col['type'] == 'decimal') {
$col['scope'] = 2;
}
}
if (isset($t['notnull'])) {
if ($t['notnull']) {
$col['require'] = true;
}
}
if (isset($t['autoincrement'])) {
$this->auto_inc_col[$table] = $name;
}
if (isset($t['flags'])) {
$flags = $t['flags'];
if (preg_match('/not[ _]null/i', $flags)) {
$col['require'] = true;
}
if (preg_match("/(auto_increment|nextval\()/i", $flags)) {
$this->auto_inc_col[$table] = $name;
}
}
$require = isset($col['require']) ? $col['require'] : false;
if ($require) {
if (isset($t['default'])) {
$default = $t['default'];
$type = $col['type'];
if (in_array($type,
array('smallint', 'integer', 'bigint'))) {
$default = (int) $default;
} elseif (in_array($type, array('single', 'double'))) {
$default = (float) $default;
} elseif ($type == 'boolean') {
$default = (int) $default ? 1 : 0;
}
$col['default'] = $default;
}
}
$this->col[$table][$name] = $col;
}
// Make array with lower case column array names as keys
$col_lc = array();
foreach ($this->col[$table] as $name => $def) {
$name_lc = strtolower($name);
$col_lc[$name_lc] = $name;
}
// Constraints/Indexes
$DB_indexes = DB_Table_Manager::getIndexes($this->db, $table);
if (PEAR::isError($DB_indexes)) {
return $DB_indexes;
}
// Check that index columns correspond to valid column names.
// Try to correct problems with capitalization, if necessary.
foreach ($DB_indexes as $type => $indexes) {
foreach ($indexes as $name => $fields) {
foreach ($fields as $key => $field) {
// If index column is not a valid column name
if (!array_key_exists($field, $this->col[$table])) {
// Try a case-insensitive match
$field_lc = strtolower($field);
if (isset($col_lc[$field_lc])) {
$correct = $col_lc[$field_lc];
$DB_indexes[$type][$name][$key]
= $correct;
} else {
$code = DB_TABLE_GENERATOR_ERR_INDEX_COL;
$return =&
DB_Table_Generator::throwError($code, $field);
}
}
}
}
}
// Generate index definitions, if any, as php code
$n_idx = 0;
$u = array();
$this->idx[$table] = array();
$this->primary_key[$table] = null;
foreach ($DB_indexes as $type => $indexes) {
if (count($indexes) > 0) {
foreach ($indexes as $name => $fields) {
$this->idx[$table][$name] = array();
$this->idx[$table][$name]['type'] = $type;
if (count($fields) == 1) {
$key = $fields[0];
} else {
$key = array();
foreach ($fields as $value) {
$key[] = $value;
}
}
$this->idx[$table][$name]['cols'] = $key;
if ($type == 'primary') {
$this->primary_key[$table] = $key;
}
}
}
}
if ($this->backend == 'mdb2') {
// Restore original MDB2 'idxname_format' and 'portability'
$this->db->setOption('idxname_format', $idxname_format);
$this->db->setOption('portability', $portability);
}
return true;
}
// }}}
// {{{ function buildTableClass($table, $indent = '')
/**
* Returns one skeleton DB_Table subclass definition, as php code
*
* The returned subclass definition string contains values for the
* $col (column), $idx (index) and $auto_inc_col properties, with
* no method definitions.
*
* @param string $table name of table
* @param string $indent string of whitespace for base indentation
*
* @return string skeleton DB_Table subclass definition
* @access public
*/
function buildTableClass($table, $indent = '')
{
$s = array();
$idx = array();
$u = array();
$v = array();
$l = 0;
$s[] = $indent . '/*';
$s[] = $indent . ' * Create the table object';
$s[] = $indent . ' */';
$s[] = $indent . 'class ' . $this->className($table)
. " extends {$this->extends} {\n";
$indent .= ' ';
$s[] = $indent . '/*';
$s[] = $indent . ' * Column definitions';
$s[] = $indent . ' */';
$s[] = $indent . 'var $col = array(' . "\n";
$indent .= ' ';
// Begin loop over columns
foreach ($this->col[$table] as $name => $col) {
// Generate DB_Table column definitions as php code
$t = array();
$t1 = array();
$l1 = 0;
$name = $indent . "'{$name}'";
$l = max($l, strlen($name));
$v[$name] = "array(\n";
$indent .= ' ';
foreach ($col as $key => $value) {
if (is_string($value)) {
$value = "'{$value}'";
} elseif (is_bool($value)) {
$value = $value ? 'true' : 'false';
} else {
$value = (string) $value;
}
$l1 = max($l1, strlen($key) + 2);
$t1[] = array("'{$key}'", $value) ;
}
foreach ($t1 as $value) {
$t[] = $indent . str_pad($value[0], $l1, ' ', STR_PAD_RIGHT)
. ' => ' . $value[1];
}
$v[$name] .= implode(",\n", $t) . "\n";
$indent = substr($indent, 0, -4);
$v[$name] .= $indent . ')';
} //end loop over columns
foreach ($v as $key => $value) {
$u[] = str_pad($key, $l, ' ', STR_PAD_RIGHT)
. ' => ' . $value;
}
$s[] = implode(",\n\n", $u) . "\n";
$indent = substr($indent, 0, -4);
$s[] = $indent . ");\n";
// Generate index definitions, if any, as php code
if (count($this->idx[$table]) > 0) {
$u = array();
$v = array();
$l = 0;
$s[] = $indent . '/*';
$s[] = $indent . ' * Index definitions';
$s[] = $indent . ' */';
$s[] = $indent . 'var $idx = array(' . "\n";
$indent .= ' ';
foreach ($this->idx[$table] as $name => $def) {
$type = $def['type'];
$cols = $def['cols'];
$name = $indent . "'{$name}'";
$l = max($l, strlen($name));
$v[$name] = "array(\n";
$indent .= ' ';
$v[$name] .= $indent . "'type' => '{$type}',\n";
if (is_array($cols)) {
$v[$name] .= $indent . "'cols' => array(\n";
$indent .= ' ';
$t = array();
foreach ($cols as $value) {
$t[] = $indent . "'{$value}'";
}
$v[$name] .= implode(",\n", $t) . "\n";
$indent = substr($indent, 0, -4);
$v[$name] .= $indent . ")\n";
} else {
$v[$name] = $v[$name] . $indent . "'cols' => '{$cols}'\n";
}
$indent = substr($indent, 0, -4);
$v[$name] .= $indent . ")";
}
foreach ($v as $key => $value) {
$u[] = str_pad($key, $l, ' ', STR_PAD_RIGHT)
. ' => ' . $value;
}
$s[] = implode(",\n\n", $u) . "\n";
$indent = substr($indent, 0, -4);
$s[] = $indent . ");\n";
} // end index generation
// Write auto_inc_col
if (isset($this->auto_inc_col[$table])) {
$s[] = $indent . '/*';
$s[] = $indent . ' * Auto-increment declaration';
$s[] = $indent . ' */';
$s[] = $indent . 'var $auto_inc_col = '
. "'{$this->auto_inc_col[$table]}';\n";
}
$indent = substr($indent, 0, -4);
$s[] = $indent . '}';
// Implode and return lines of class definition
return implode("\n", $s) . "\n";
}
// }}}
// {{{ function buildTableClasses()
/**
* Returns a string containing all table class definitions in one file
*
* The returned string contains the contents of a single php file with
* definitions of DB_Table subclasses associated with all of the tables
* in $this->tables. If $this->tables is initially null, method
* $this->getTableNames() is called internally to generate a list of
* table names.
*
* The returned string includes the opening and closing <?php and ?>
* script elements, and the require_once line needed to include the
* $this->extend_class (i.e., DB_Table or a subclass) that is being
* extended. To use, write this string to a new php file.
*
* Usage:
* <code>
* $generator = new DB_Table_Generator($db, $database);
* echo $generator->buildTablesClasses();
* </code>
*
* @return mixed a string with all table class definitions,
* PEAR Error on failure
* @access public
*/
function buildTableClasses()
{
// If $this->tables is null, call getTableNames()
if (!$this->tables) {
$return = $this->getTableNames();
if (PEAR::isError($return)) {
return $return;
}
}
$s = array();
$s[] = '<?php';
$s[] = '/*';
$s[] = ' * Include basic class';
$s[] = ' */';
$s[] = "require_once '{$this->extends_file}';\n";
foreach ($this->tables as $table) {
$return = $this->getTableDefinition($table);
if (PEAR::isError($return)) {
return $return;
}
$s[] = $this->buildTableClass($table) . "\n";
}
$s[] = '?>';
return implode("\n", $s);
}
// }}}
// {{{ function generateTableClassFiles()
/**
* Writes all table class definitions to separate files
*
* Usage:
* <code>
* $generator = new DB_Table_Generator($db, $database);
* $generator->generateTableClassFiles();
* </code>
*
* @return mixed true on success, PEAR Error on failure
* @access public
*/
function generateTableClassFiles()
{
// If $this->tables is null, call getTableNames()
if (!$this->tables) {
$return = $this->getTableNames();
if (PEAR::isError($return)) {
return $return;
}
}
// Write all table class definitions to separate files
foreach ($this->tables as $table) {
$classname = $this->className($table);
$filename = $this->classFileName($classname);
$base = $this->class_write_path;
if ($base) {
if (!file_exists($base)) {
include_once 'System.php';
if (!@System::mkdir(array('-p', $base))) {
return $this->throwError(DB_TABLE_GENERATOR_ERR_FILE,
$base);
}
}
$filename = "{$base}/{$filename}";
}
if (!file_exists($filename)) {
$s = array();
$s[] = '<?php';
$s[] = '/*';
$s[] = ' * Include basic class';
$s[] = ' */';
$s[] = "require_once '{$this->extends_file}';\n";
$return = $this->getTableDefinition($table);
if (PEAR::isError($return)) {
return $return;
}
$s[] = $this->buildTableClass($table);
$s[] = '?>';
$s[] = '';
$out = implode("\n", $s);
if (!$file = @fopen($filename, 'wb')) {
return $this->throwError(DB_TABLE_GENERATOR_ERR_FILE,
$filename);
}
fputs($file, $out);
fclose($file);
}
}
return true;
}
// }}}
// {{{ function generateDatabaseFile($object_name = null)
/**
* Writes a file to instantiate Table and Database objects
*
* After successful completion, a file named 'Database.php' will be
* have been created in the $this->class_write_path directory. This
* file should normally be included in application php scripts. It
* can be renamed by the user.
*
* Usage:
* <code>
* $generator = new DB_Table_Generator($db, $database);
* $generator->generateTableClassFiles();
* $generator->generateDatabaseFile();
* </code>
*
* @param string $object_name variable name for DB_Table_Database object
*
* @return mixed true on success, PEAR Error on failure
* @access public
*/
function generateDatabaseFile($object_name = null)
{
// Set name for DB_Table_Database object
if ($object_name) {
$object_name = "\${$object_name}";
} else {
$object_name = '$db'; //default
}
$backend = strtoupper($this->backend); // 'DB' or 'MDB2'
if ('DB' == $backend) {
$dsn = $this->db->dsn;
} else {
$dsn = $this->db->getDSN('array');
}
// Create array d[] containing lines of database php file
$d = array();
$d[] = '<?php';
$d[] = '/*';
$d[] = ' * Include basic classes';
$d[] = ' */';
$d[] = "require_once '{$backend}.php';";
$d[] = "require_once 'DB/Table/Database.php';";
// Require_once statements for subclass definitions
foreach ($this->tables as $table) {
$classname = $this->className($table);
$class_filename = $this->classFileName($classname);
if ($this->class_include_path) {
$d[] = 'require_once '
. "'{$this->class_include_path}/{$class_filename}';";
} else {
$d[] = "require_once '{$class_filename}';";
}
}
$d[] = '';
$d[] = '/*';
$d[] = ' * NOTE: User must uncomment & edit code to create $dsn';
$d[] = ' */';
$d[] = "//\$phptype = '{$dsn['phptype']}';";
$d[] = "//\$username = '{$dsn['username']}';";
$d[] = "//\$password = ''; // put your password here";
$d[] = "//\$hostname = '{$dsn['hostspec']}';";
$d[] = "//\$database = '{$dsn['database']}';";
$d[] = "//\$create = false; // 'drop', 'safe', 'verify', 'alter'";
$d[] = '//$dsn = "{$phptype}://{$username}:{$password}@{$hostname}'
. '/{$database}";';
$d[] = '';
$d[] = '/*';
$d[] = " * Instantiate {$backend} connection object \$conn";
$d[] = ' */';
$d[] = "\$conn =& {$backend}::connect(\$dsn);";
$d[] = 'if (PEAR::isError($conn)) {';
$d[] = ' echo "Error connecting to database server\n";';
$d[] = ' echo $conn->getMessage();';
$d[] = ' die;';
$d[] = '}';
$d[] = '';
$d[] = '/*';
$d[] = ' * Create one instance of each DB_Table subclass';
$d[] = ' */';
foreach ($this->tables as $table) {
$classname = $this->className($table);
$d[] = "\${$table} = new {$classname}("
. '$conn, ' . "'{$table}'" . ', $create);';
$d[] = "if (PEAR::isError(\${$table}->error)) {";
$d[] = ' echo "Can\'t create table object.\n";';
$d[] = " echo \${$table}->error->getMessage();";
$d[] = ' die;';
$d[] = '}';
}
$d[] = '';
$d[] = '/*';
$d[] = ' * Instantiate a parent DB_Table_Database object';
$d[] = ' */';
$d[] = "{$object_name} = new DB_Table_Database(\$conn, \$database);";
$d[] = "if (PEAR::isError({$object_name}->error)) {";
$d[] = ' echo "Can\'t create database object.\n";';
$d[] = " echo {$object_name}->error->getMessage();";
$d[] = ' die;';
$d[] = '}';
$d[] = '';
$d[] = '/*';
$d[] = ' * Add DB_Table objects to parent DB_Table_Database object';
$d[] = ' */';
foreach ($this->tables as $table) {
$classname = $this->className($table);
$d[] = "\$result = {$object_name}->addTable(\${$table});";
$d[] = 'if (PEAR::isError($result)) {';
$d[] = ' echo "Can\'t add table object to database object.\n";';
$d[] = ' echo $result->getMessage();';
$d[] = ' die;';
$d[] = '}';
}
$d[] = '';
// Add foreign key references: If the name of an integer column
// matches "/id$/i" (i.e., the names ends with id, ID, or Id), the
// remainder of the name matches the name $rtable of another table,
// and $rtable has an integer primary key, then the column is
// assumed to be a foreign key that references $rtable.
$d[] = '/*';
$d[] = ' * Add auto-guessed foreign references';
$d[] = ' */';
foreach ($this->col as $table => $col) {
foreach ($col as $col_name => $def) {
// Only consider integer columns
$ftype = $def['type'];
if (!in_array($ftype, array('integer','smallint','bigint'))) {
continue;
}
if (preg_match("/id$/i", $col_name)) {
$column_base = preg_replace('/_?id$/i', '', $col_name);
foreach ($this->tables as $rtable) {
if (!preg_match("/^{$rtable}$/i", $column_base)) {
continue;
}
if (preg_match("/^{$table}$/i", $column_base)) {
continue;
}
if (!isset($this->primary_key[$rtable])) {
continue;
}
$rkey = $this->primary_key[$rtable];
if (is_array($rkey)) {
continue;
}
$rtype = $this->col[$rtable][$rkey]['type'];
if (!in_array($rtype,
array('integer','smallint','bigint'))) {
continue;
}
$d[] = "\$result = {$object_name}->addRef('{$table}', "
. "'{$col_name}', '{$rtable}');";
$d[] = 'if (PEAR::isError($result)) {';
$d[] = ' echo "Can\'t add foreign key reference.\n";';
$d[] = ' echo $result->getMessage();';
$d[] = ' die;';
$d[] = '}';
}
}
}
}
$d[] = '';
$d[] = '/*';
$d[] = ' * Add any additional foreign key references here';
$d[] = ' *';
$d[] = ' * Add any linking table declarations here';
$d[] = ' * Uncomment next line to add all possible linking tables;';
$d[] = ' */';
$d[] = "//\$result = {$object_name}->addAllLinks();";
$d[] = '//if (PEAR::isError($result)) {';
$d[] = '// echo "Can\'t add linking tables.\n";';
$d[] = '// echo $result->getMessage();';
$d[] = '// die;';
$d[] = '//}';
$d[] = '';
// Closing script element
$d[] = '?>';
$d[] = '';
// Open and write file
$base = $this->class_write_path;
if ($base) {
if (!file_exists($base)) {
include_once 'System.php';
if (!@System::mkdir(array('-p', $base))) {
return $this->throwError(DB_TABLE_GENERATOR_ERR_FILE, $base);
}
}
$filename = $base . '/Database.php';
} else {
$filename = 'Database.php';
}
if (!$file = @fopen($filename, 'wb')) {
return $this->throwError(DB_TABLE_GENERATOR_ERR_FILE, $filename);
}
$out = implode("\n", $d);
fputs($file, $out);
fclose($file);
return true;
}
// }}}
// {{{ function className($table)
/**
* Convert a table name into a class name
*
* Converts all non-alphanumeric characters to '_', capitalizes
* first letter, and adds $this->class_suffix to end. Override
* this if you want something else.
*
* @param string $table name of table
*
* @return string class name;
* @access public
*/
function className($table)
{
$name = preg_replace('/[^A-Z0-9]/i', '_', ucfirst(trim($table)));
return $name . $this->class_suffix;
}
// }}}
// {{{ function tableName($table)
/**
* Returns a valid variable name from a table name
*
* Converts all non-alphanumeric characters to '_'. Override
* this if you want something else.
*
* @param string $table name of table
*
* @return string variable name;
* @access public
*/
function tableName($table)
{
return preg_replace('/[^A-Z0-9]/i', '_', trim($table));
}
// }}}
// {{{ function classFileName($class_name)
/**
* Returns the path to a file containing a class definition
*
* Appends '.php' to class name.
*
* @param string $class_name name of class
*
* @return string file name
* @access public
*/
function classFileName($class_name)
{
$filename = $class_name . '.php';
return $filename;
}
// }}}
}
// }}}