38f2ecefac
Also reformatted under PSR norms
1760 lines
62 KiB
PHP
1760 lines
62 KiB
PHP
<?php
|
||
/**
|
||
* Generation tools for DB_DataObject
|
||
*
|
||
* PHP versions 4 and 5
|
||
*
|
||
* LICENSE: This source file is subject to version 3.01 of the PHP license
|
||
* that is available through the world-wide-web at the following URI:
|
||
* http://www.php.net/license/3_01.txt. If you did not receive a copy of
|
||
* the PHP License and are unable to obtain it through the web, please
|
||
* send a note to license@php.net so we can mail you a copy immediately.
|
||
*
|
||
* @category Database
|
||
* @package DB_DataObject
|
||
* @author Alan Knowles <alan@akbkhome.com>
|
||
* @copyright 1997-2006 The PHP Group
|
||
* @license http://www.php.net/license/3_01.txt PHP License 3.01
|
||
* @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $
|
||
* @link http://pear.php.net/package/DB_DataObject
|
||
*/
|
||
|
||
/*
|
||
* Security Notes:
|
||
* This class uses eval to create classes on the fly.
|
||
* The table name and database name are used to check the database before writing the
|
||
* class definitions, we now check for quotes and semi-colon's in both variables
|
||
* so I cant see how it would be possible to generate code even if
|
||
* for some crazy reason you took the classname and table name from User Input.
|
||
*
|
||
* If you consider that wrong, or can prove it.. let me know!
|
||
*/
|
||
|
||
/**
|
||
*
|
||
* Config _$ptions
|
||
* [DB_DataObject]
|
||
* ; optional default = DB/DataObject.php
|
||
* extends_location =
|
||
* ; optional default = DB_DataObject
|
||
* extends =
|
||
* ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
|
||
* generator_class_rewrite = ANY|specific_name // default is DB_DataObject
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* Needed classes
|
||
* We lazy load here, due to problems with the tests not setting up include path correctly.
|
||
* FIXME!
|
||
*/
|
||
class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
|
||
//require_once('Config.php');
|
||
|
||
/**
|
||
* Generator class
|
||
*
|
||
* @package DB_DataObject
|
||
*/
|
||
class DB_DataObject_Generator extends DB_DataObject
|
||
{
|
||
/* =========================================================== */
|
||
/* Utility functions - for building db config files */
|
||
/* =========================================================== */
|
||
|
||
/**
|
||
* Array of table names
|
||
*
|
||
* @var array
|
||
* @access private
|
||
*/
|
||
public $tables;
|
||
|
||
/**
|
||
* associative array table -> array of table row objects
|
||
*
|
||
* @var array
|
||
* @access private
|
||
*/
|
||
public $_definitions;
|
||
|
||
/**
|
||
* active table being output
|
||
*
|
||
* @var string
|
||
* @access private
|
||
*/
|
||
public $table; // active tablename
|
||
|
||
/**
|
||
* links (generated)
|
||
*
|
||
* @var array
|
||
* @access private
|
||
*/
|
||
public $_fkeys; // active tablename
|
||
|
||
/**
|
||
* The 'starter' = call this to start the process
|
||
*
|
||
* @access public
|
||
* @return none
|
||
*/
|
||
public function start()
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
|
||
|
||
$databases = array();
|
||
foreach ($options as $k=>$v) {
|
||
if (substr($k, 0, 9) == 'database_') {
|
||
$databases[substr($k, 9)] = $v;
|
||
}
|
||
}
|
||
|
||
if (isset($options['database'])) {
|
||
if ($db_driver == 'DB') {
|
||
require_once 'DB.php';
|
||
$dsn = DB::parseDSN($options['database']);
|
||
} else {
|
||
require_once 'MDB2.php';
|
||
$dsn = MDB2::parseDSN($options['database']);
|
||
}
|
||
|
||
if (!isset($database[$dsn['database']])) {
|
||
$databases[$dsn['database']] = $options['database'];
|
||
}
|
||
}
|
||
|
||
foreach ($databases as $databasename => $database) {
|
||
if (!$database) {
|
||
continue;
|
||
}
|
||
$this->debug("CREATING FOR $databasename\n");
|
||
$class = get_class($this);
|
||
$t = new $class;
|
||
$t->_database_dsn = $database;
|
||
|
||
|
||
$t->_database = $databasename;
|
||
if ($db_driver == 'DB') {
|
||
require_once 'DB.php';
|
||
$dsn = DB::parseDSN($database);
|
||
} else {
|
||
require_once 'MDB2.php';
|
||
$dsn = MDB2::parseDSN($database);
|
||
}
|
||
|
||
if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
|
||
$t->_database = basename($t->_database);
|
||
}
|
||
$t->_createTableList();
|
||
$t->_createForiegnKeys();
|
||
|
||
foreach (get_class_methods($class) as $method) {
|
||
if (substr($method, 0, 8) != 'generate') {
|
||
continue;
|
||
}
|
||
$this->debug("calling $method");
|
||
$t->$method();
|
||
}
|
||
}
|
||
$this->debug("DONE\n\n");
|
||
}
|
||
|
||
/**
|
||
* Output File was config object, now just string
|
||
* Used to generate the Tables
|
||
*
|
||
* @var string outputbuffer for table definitions
|
||
* @access private
|
||
*/
|
||
public $_newConfig;
|
||
|
||
/**
|
||
* Build a list of tables;
|
||
* and store it in $this->tables and $this->_definitions[tablename];
|
||
*
|
||
* @access private
|
||
* @return none
|
||
*/
|
||
public function _createTableList()
|
||
{
|
||
$this->_connect();
|
||
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
|
||
|
||
|
||
$__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
|
||
|
||
$db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
|
||
$is_MDB2 = ($db_driver != 'DB') ? true : false;
|
||
|
||
if (is_object($__DB) && is_a($__DB, 'PEAR_Error')) {
|
||
return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
|
||
}
|
||
|
||
if (!$is_MDB2) {
|
||
// try getting a list of schema tables first. (postgres)
|
||
$__DB->expectError(DB_ERROR_UNSUPPORTED);
|
||
$this->tables = $__DB->getListOf('schema.tables');
|
||
$__DB->popExpect();
|
||
} else {
|
||
/**
|
||
* set portability and some modules to fetch the informations
|
||
*/
|
||
$db_options = PEAR::getStaticProperty('MDB2', 'options');
|
||
if (empty($db_options)) {
|
||
$__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
|
||
}
|
||
|
||
$__DB->loadModule('Manager');
|
||
$__DB->loadModule('Reverse');
|
||
}
|
||
|
||
if ((empty($this->tables) || (is_object($this->tables) && is_a($this->tables, 'PEAR_Error')))) {
|
||
//if that fails fall back to clasic tables list.
|
||
if (!$is_MDB2) {
|
||
// try getting a list of schema tables first. (postgres)
|
||
$__DB->expectError(DB_ERROR_UNSUPPORTED);
|
||
$this->tables = $__DB->getListOf('tables');
|
||
$__DB->popExpect();
|
||
} else {
|
||
$this->tables = $__DB->manager->listTables();
|
||
$sequences = $__DB->manager->listSequences();
|
||
foreach ($sequences as $k => $v) {
|
||
$this->tables[] = $__DB->getSequenceName($v);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (is_object($this->tables) && is_a($this->tables, 'PEAR_Error')) {
|
||
return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
|
||
}
|
||
|
||
// build views as well if asked to.
|
||
if (!empty($options['build_views'])) {
|
||
if (!$is_MDB2) {
|
||
$views = $__DB->getListOf(is_string($options['build_views']) ?
|
||
$options['build_views'] : 'views');
|
||
} else {
|
||
$views = $__DB->manager->listViews();
|
||
}
|
||
if (is_object($views) && is_a($views, 'PEAR_Error')) {
|
||
return PEAR::raiseError(
|
||
'Error getting Views (check the PEAR bug database for the fix to DB), ' .
|
||
$views->toString(),
|
||
null,
|
||
PEAR_ERROR_DIE
|
||
);
|
||
}
|
||
$this->tables = array_merge($this->tables, $views);
|
||
}
|
||
|
||
// declare a temporary table to be filled with matching tables names
|
||
$tmp_table = array();
|
||
|
||
|
||
foreach ($this->tables as $table) {
|
||
if (isset($options['generator_include_regex']) &&
|
||
!preg_match($options['generator_include_regex'], $table)) {
|
||
$this->debug("SKIPPING (generator_include_regex) : $table");
|
||
continue;
|
||
}
|
||
|
||
if (isset($options['generator_exclude_regex']) &&
|
||
preg_match($options['generator_exclude_regex'], $table)) {
|
||
continue;
|
||
}
|
||
|
||
$strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema'];
|
||
$strip = is_numeric($strip) ? (bool) $strip : $strip;
|
||
$strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip;
|
||
|
||
// postgres strip the schema bit from the
|
||
if (!empty($strip)) {
|
||
if (!is_string($strip) || preg_match($strip, $table)) {
|
||
$bits = explode('.', $table, 2);
|
||
$table = $bits[0];
|
||
if (count($bits) > 1) {
|
||
$table = $bits[1];
|
||
}
|
||
}
|
||
}
|
||
$this->debug("EXTRACTING : $table");
|
||
|
||
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
|
||
$__DB->quoteIdentifier($table) : $table;
|
||
|
||
if (!$is_MDB2) {
|
||
$defs = $__DB->tableInfo($quotedTable);
|
||
} else {
|
||
$defs = $__DB->reverse->tableInfo($quotedTable);
|
||
// rename the length value, so it matches db's return.
|
||
}
|
||
|
||
if (is_object($defs) && is_a($defs, 'PEAR_Error')) {
|
||
// running in debug mode should pick this up as a big warning..
|
||
$this->debug("Error reading tableInfo: $table");
|
||
$this->raiseError('Error reading tableInfo, '. $defs->toString());
|
||
continue;
|
||
}
|
||
// cast all definitions to objects - as we deal with that better.
|
||
|
||
|
||
|
||
foreach ($defs as $def) {
|
||
if (!is_array($def)) {
|
||
continue;
|
||
}
|
||
// rename the length value, so it matches db's return.
|
||
if (isset($def['length']) && !isset($def['len'])) {
|
||
$def['len'] = $def['length'];
|
||
}
|
||
$this->_definitions[$table][] = (object) $def;
|
||
}
|
||
// we find a matching table, just store it into a temporary array
|
||
$tmp_table[] = $table;
|
||
}
|
||
// the temporary table array is now the right one (tables names matching
|
||
// with regex expressions have been removed)
|
||
$this->tables = $tmp_table;
|
||
|
||
//print_r($this->_definitions);
|
||
}
|
||
|
||
/**
|
||
* Auto generation of table data.
|
||
*
|
||
* it will output to db_oo_{database} the table definitions
|
||
*
|
||
* @access private
|
||
* @return none
|
||
*/
|
||
public function generateDefinitions()
|
||
{
|
||
$this->debug("Generating Definitions file: ");
|
||
if (!$this->tables) {
|
||
$this->debug("-- NO TABLES -- \n");
|
||
return;
|
||
}
|
||
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
|
||
|
||
//$this->_newConfig = new Config('IniFile');
|
||
$this->_newConfig = '';
|
||
foreach ($this->tables as $this->table) {
|
||
$this->_generateDefinitionsTable();
|
||
}
|
||
$this->_connect();
|
||
// dont generate a schema if location is not set
|
||
// it's created on the fly!
|
||
if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"])) {
|
||
return;
|
||
}
|
||
if (!empty($options['generator_no_ini'])) { // built in ini files..
|
||
return;
|
||
}
|
||
$base = @$options['schema_location'];
|
||
if (isset($options["ini_{$this->_database}"])) {
|
||
$file = $options["ini_{$this->_database}"];
|
||
} else {
|
||
$file = "{$base}/{$this->_database}.ini";
|
||
}
|
||
|
||
if (!file_exists(dirname($file))) {
|
||
require_once 'System.php';
|
||
System::mkdir(array('-p','-m',0755,dirname($file)));
|
||
}
|
||
$this->debug("Writing ini as {$file}\n");
|
||
//touch($file);
|
||
$tmpname = tempnam(session_save_path(), 'DataObject_');
|
||
//print_r($this->_newConfig);
|
||
$fh = fopen($tmpname, 'w');
|
||
if (!$fh) {
|
||
return PEAR::raiseError(
|
||
"Failed to create temporary file: $tmpname\n".
|
||
"make sure session.save_path is set and is writable\n",
|
||
null,
|
||
PEAR_ERROR_DIE
|
||
);
|
||
}
|
||
fwrite($fh, $this->_newConfig);
|
||
fclose($fh);
|
||
$perms = file_exists($file) ? fileperms($file) : 0755;
|
||
// windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
|
||
|
||
if (!@rename($tmpname, $file)) {
|
||
unlink($file);
|
||
rename($tmpname, $file);
|
||
}
|
||
chmod($file, $perms);
|
||
//$ret = $this->_newConfig->writeInput($file,false);
|
||
|
||
//if (PEAR::isError($ret) ) {
|
||
// return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
|
||
// }
|
||
}
|
||
/**
|
||
* create the data for Foreign Keys (for links.ini)
|
||
* Currenly only works with mysql / mysqli / posgtreas
|
||
* to use, you must set option: generate_links=true
|
||
*
|
||
* @author Pascal Sch<63>ni
|
||
*/
|
||
|
||
public function _createForiegnKeys()
|
||
{
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
if (empty($options['generate_links'])) {
|
||
return false;
|
||
}
|
||
$__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
|
||
if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
|
||
echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
|
||
return; // cant handle non-mysql introspection for defaults.
|
||
}
|
||
$this->debug("generateForeignKeys: Start");
|
||
$DB = $this->getDatabaseConnection();
|
||
|
||
$fk = array();
|
||
|
||
|
||
switch ($DB->phptype) {
|
||
|
||
|
||
case 'pgsql':
|
||
foreach ($this->tables as $this->table) {
|
||
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
|
||
$res =& $DB->query("SELECT
|
||
pg_catalog.pg_get_constraintdef(r.oid, true) AS condef
|
||
FROM pg_catalog.pg_constraint r,
|
||
pg_catalog.pg_class c
|
||
WHERE c.oid=r.conrelid
|
||
AND r.contype = 'f'
|
||
AND c.relname = '" . $quotedTable . "'");
|
||
if (PEAR::isError($res)) {
|
||
die($res->getMessage());
|
||
}
|
||
|
||
while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
|
||
$treffer = array();
|
||
// this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049
|
||
preg_match(
|
||
"/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i",
|
||
$row['condef'],
|
||
$treffer
|
||
);
|
||
if (!count($treffer)) {
|
||
continue;
|
||
}
|
||
$fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3];
|
||
}
|
||
}
|
||
break;
|
||
|
||
|
||
case 'mysql':
|
||
case 'mysqli':
|
||
default:
|
||
|
||
foreach ($this->tables as $this->table) {
|
||
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
|
||
|
||
$res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable);
|
||
|
||
if (PEAR::isError($res)) {
|
||
die($res->getMessage());
|
||
}
|
||
|
||
$text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
|
||
$treffer = array();
|
||
// Extract FOREIGN KEYS
|
||
preg_match_all(
|
||
"/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
|
||
$text[1],
|
||
$treffer,
|
||
PREG_SET_ORDER
|
||
);
|
||
|
||
if (!count($treffer)) {
|
||
continue;
|
||
}
|
||
foreach ($treffer as $i=> $tref) {
|
||
$fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
$this->_fkeys = $fk;
|
||
}
|
||
|
||
|
||
/**
|
||
* generate Foreign Keys (for links.ini)
|
||
* Currenly only works with mysql / mysqli
|
||
* to use, you must set option: generate_links=true
|
||
*
|
||
* @author Pascal Sch<63>ni
|
||
*/
|
||
public function generateForeignKeys()
|
||
{
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
if (empty($options['generate_links'])) {
|
||
return false;
|
||
}
|
||
$__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
|
||
if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
|
||
echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
|
||
return; // cant handle non-mysql introspection for defaults.
|
||
}
|
||
$this->debug("generateForeignKeys: Start");
|
||
|
||
$fk = $this->_fkeys;
|
||
$links_ini = "";
|
||
|
||
foreach ($fk as $table => $details) {
|
||
$links_ini .= "[$table]\n";
|
||
foreach ($details as $col => $ref) {
|
||
$links_ini .= "$col = $ref\n";
|
||
}
|
||
$links_ini .= "\n";
|
||
}
|
||
|
||
// dont generate a schema if location is not set
|
||
// it's created on the fly!
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
|
||
if (!empty($options['schema_location'])) {
|
||
$file = "{$options['schema_location']}/{$this->_database}.links.ini";
|
||
} elseif (isset($options["ini_{$this->_database}"])) {
|
||
$file = preg_replace('/\.ini/', '.links.ini', $options["ini_{$this->_database}"]);
|
||
} else {
|
||
$this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set");
|
||
return;
|
||
}
|
||
|
||
|
||
if (!file_exists(dirname($file))) {
|
||
mkdir(dirname($file), 0755, true);
|
||
}
|
||
|
||
$this->debug("Writing ini as {$file}\n");
|
||
|
||
//touch($file); // not sure why this is needed?
|
||
$tmpname = tempnam(session_save_path(), 'DataObject_');
|
||
|
||
$fh = fopen($tmpname, 'w');
|
||
if (!$fh) {
|
||
return PEAR::raiseError(
|
||
"Failed to create temporary file: $tmpname\n".
|
||
"make sure session.save_path is set and is writable\n",
|
||
null,
|
||
PEAR_ERROR_DIE
|
||
);
|
||
}
|
||
fwrite($fh, $links_ini);
|
||
fclose($fh);
|
||
$perms = file_exists($file) ? fileperms($file) : 0755;
|
||
// windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
|
||
if (!@rename($tmpname, $file)) {
|
||
unlink($file);
|
||
rename($tmpname, $file);
|
||
}
|
||
chmod($file, $perms);
|
||
}
|
||
|
||
|
||
/**
|
||
* The table geneation part
|
||
*
|
||
* @access private
|
||
* @return tabledef and keys array.
|
||
*/
|
||
public function _generateDefinitionsTable()
|
||
{
|
||
global $_DB_DATAOBJECT;
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$defs = $this->_definitions[$this->table];
|
||
$this->_newConfig .= "\n[{$this->table}]\n";
|
||
$keys_out = "\n[{$this->table}__keys]\n";
|
||
$keys_out_primary = '';
|
||
$keys_out_secondary = '';
|
||
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
|
||
echo "TABLE STRUCTURE FOR {$this->table}\n";
|
||
print_r($defs);
|
||
}
|
||
$DB = $this->getDatabaseConnection();
|
||
$dbtype = $DB->phptype;
|
||
|
||
$ret = array(
|
||
'table' => array(),
|
||
'keys' => array(),
|
||
);
|
||
|
||
$ret_keys_primary = array();
|
||
$ret_keys_secondary = array();
|
||
|
||
|
||
|
||
foreach ($defs as $t) {
|
||
$n=0;
|
||
$write_ini = true;
|
||
|
||
|
||
switch (strtoupper($t->type)) {
|
||
|
||
case 'INT':
|
||
case 'INT2': // postgres
|
||
case 'INT4': // postgres
|
||
case 'INT8': // postgres
|
||
case 'SERIAL4': // postgres
|
||
case 'SERIAL8': // postgres
|
||
case 'INTEGER':
|
||
case 'TINYINT':
|
||
case 'SMALLINT':
|
||
case 'MEDIUMINT':
|
||
case 'BIGINT':
|
||
$type = DB_DATAOBJECT_INT;
|
||
if ($t->len == 1) {
|
||
$type += DB_DATAOBJECT_BOOL;
|
||
}
|
||
break;
|
||
|
||
case 'REAL':
|
||
case 'DOUBLE':
|
||
case 'DOUBLE PRECISION': // double precision (firebird)
|
||
case 'FLOAT':
|
||
case 'FLOAT4': // real (postgres)
|
||
case 'FLOAT8': // double precision (postgres)
|
||
case 'DECIMAL':
|
||
case 'MONEY': // mssql and maybe others
|
||
case 'NUMERIC':
|
||
case 'NUMBER': // oci8
|
||
$type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
|
||
break;
|
||
|
||
case 'YEAR':
|
||
$type = DB_DATAOBJECT_INT;
|
||
break;
|
||
|
||
case 'BIT':
|
||
case 'BOOL':
|
||
case 'BOOLEAN':
|
||
|
||
$type = DB_DATAOBJECT_BOOL;
|
||
// postgres needs to quote '0'
|
||
if ($dbtype == 'pgsql') {
|
||
$type += DB_DATAOBJECT_STR;
|
||
}
|
||
break;
|
||
|
||
case 'STRING':
|
||
case 'CHAR':
|
||
case 'VARCHAR':
|
||
case 'VARCHAR2':
|
||
case 'TINYTEXT':
|
||
|
||
case 'ENUM':
|
||
case 'SET': // not really but oh well
|
||
|
||
case 'POINT': // mysql geometry stuff - not really string - but will do..
|
||
|
||
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
|
||
|
||
$type = DB_DATAOBJECT_STR;
|
||
break;
|
||
|
||
case 'TEXT':
|
||
case 'MEDIUMTEXT':
|
||
case 'LONGTEXT':
|
||
case '_TEXT': //postgres (?? view ??)
|
||
|
||
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
|
||
break;
|
||
|
||
|
||
case 'DATE':
|
||
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
|
||
break;
|
||
|
||
case 'TIME':
|
||
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
|
||
break;
|
||
|
||
|
||
case 'DATETIME':
|
||
|
||
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
|
||
break;
|
||
|
||
case 'TIMESTAMP': // do other databases use this???
|
||
|
||
$type = ($dbtype == 'mysql') ?
|
||
DB_DATAOBJECT_MYSQLTIMESTAMP :
|
||
DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
|
||
break;
|
||
|
||
|
||
case 'BLOB': /// these should really be ignored!!!???
|
||
case 'TINYBLOB':
|
||
case 'MEDIUMBLOB':
|
||
case 'LONGBLOB':
|
||
|
||
case 'CLOB': // oracle character lob support
|
||
|
||
case 'BYTEA': // postgres blob support..
|
||
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
|
||
break;
|
||
|
||
default:
|
||
echo "*****************************************************************\n".
|
||
"** WARNING UNKNOWN TYPE **\n".
|
||
"** Found column '{$t->name}', of type '{$t->type}' **\n".
|
||
"** Please submit a bug, describe what type you expect this **\n".
|
||
"** column to be **\n".
|
||
"** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
|
||
"** Try using MDB2 as the backend - eg set the config option **\n".
|
||
"** db_driver = MDB2 **\n".
|
||
"*****************************************************************\n";
|
||
$write_ini = false;
|
||
break;
|
||
}
|
||
|
||
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
|
||
echo "*****************************************************************\n".
|
||
"** WARNING COLUMN NAME UNUSABLE **\n".
|
||
"** Found column '{$t->name}', of type '{$t->type}' **\n".
|
||
"** Since this column name can't be converted to a php variable **\n".
|
||
"** name, and the whole idea of mapping would result in a mess **\n".
|
||
"** This column has been ignored... **\n".
|
||
"*****************************************************************\n";
|
||
continue;
|
||
}
|
||
|
||
if (!strlen(trim($t->name))) {
|
||
continue; // is this a bug?
|
||
}
|
||
|
||
if (preg_match('/not[ _]null/i', $t->flags)) {
|
||
$type += DB_DATAOBJECT_NOTNULL;
|
||
}
|
||
|
||
|
||
if (in_array($t->name, array('null','yes','no','true','false'))) {
|
||
echo "*****************************************************************\n".
|
||
"** WARNING **\n".
|
||
"** Found column '{$t->name}', which is invalid in an .ini file **\n".
|
||
"** This line will not be writen to the file - you will have **\n".
|
||
"** define the keys()/method manually. **\n".
|
||
"*****************************************************************\n";
|
||
$write_ini = false;
|
||
} else {
|
||
$this->_newConfig .= "{$t->name} = $type\n";
|
||
}
|
||
|
||
$ret['table'][$t->name] = $type;
|
||
// i've no idea if this will work well on other databases?
|
||
// only use primary key or nextval(), cause the setFrom blocks you setting all key items...
|
||
// if no keys exist fall back to using unique
|
||
//echo "\n{$t->name} => {$t->flags}\n";
|
||
$secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique';
|
||
|
||
$m = array();
|
||
if (preg_match('/(auto_increment|nextval\(([^)]*))/i', rawurldecode($t->flags), $m)
|
||
|| (isset($t->autoincrement) && ($t->autoincrement === true))) {
|
||
$sn = 'N';
|
||
if ($DB->phptype == 'pgsql' && !empty($m[2])) {
|
||
$sn = preg_replace('/[("]+/', '', $m[2]);
|
||
//echo urldecode($t->flags) . "\n" ;
|
||
}
|
||
// native sequences = 2
|
||
if ($write_ini) {
|
||
$keys_out_primary .= "{$t->name} = $sn\n";
|
||
}
|
||
$ret_keys_primary[$t->name] = $sn;
|
||
} elseif ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i', $t->flags)) {
|
||
// keys.. = 1
|
||
$key_type = 'K';
|
||
if (!preg_match("/(primary)/i", $t->flags)) {
|
||
$key_type = 'U';
|
||
}
|
||
|
||
if ($write_ini) {
|
||
$keys_out_secondary .= "{$t->name} = {$key_type}\n";
|
||
}
|
||
$ret_keys_secondary[$t->name] = $key_type;
|
||
}
|
||
}
|
||
|
||
$this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
|
||
$ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
|
||
|
||
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
|
||
print_r(array("dump for {$this->table}", $ret));
|
||
}
|
||
|
||
return $ret;
|
||
}
|
||
|
||
/**
|
||
* Convert a table name into a class name -> override this if you want a different mapping
|
||
*
|
||
* @access public
|
||
* @return string class name;
|
||
*/
|
||
|
||
|
||
public function getClassNameFromTableName($table)
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
|
||
return $class_prefix.preg_replace('/[^A-Z0-9]/i', '_', ucfirst(trim($this->table)));
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a table name into a file name -> override this if you want a different mapping
|
||
*
|
||
* @access public
|
||
* @return string file name;
|
||
*/
|
||
|
||
|
||
public function getFileNameFromTableName($table)
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$base = $options['class_location'];
|
||
if (strpos($base, '%s') !== false) {
|
||
$base = dirname($base);
|
||
}
|
||
if (!file_exists($base)) {
|
||
require_once 'System.php';
|
||
System::mkdir(array('-p',$base));
|
||
}
|
||
if (strpos($options['class_location'], '%s') !== false) {
|
||
$outfilename = sprintf(
|
||
$options['class_location'],
|
||
preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table))
|
||
);
|
||
} else {
|
||
$outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table)).".php";
|
||
}
|
||
return $outfilename;
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
|
||
*
|
||
* @access public
|
||
* @return string method name;
|
||
*/
|
||
|
||
|
||
public function getMethodNameFromColumnName($col)
|
||
{
|
||
return ucfirst($col);
|
||
}
|
||
|
||
|
||
|
||
|
||
/*
|
||
* building the class files
|
||
* for each of the tables output a file!
|
||
*/
|
||
public function generateClasses()
|
||
{
|
||
//echo "Generating Class files: \n";
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
|
||
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
|
||
$this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
|
||
|
||
|
||
foreach ($this->tables as $this->table) {
|
||
$this->table = trim($this->table);
|
||
$this->classname = $this->getClassNameFromTableName($this->table);
|
||
$i = '';
|
||
$outfilename = $this->getFileNameFromTableName($this->table);
|
||
|
||
$oldcontents = '';
|
||
if (file_exists($outfilename)) {
|
||
// file_get_contents???
|
||
$oldcontents = implode('', file($outfilename));
|
||
}
|
||
|
||
$out = $this->_generateClassTable($oldcontents);
|
||
$this->debug("writing $this->classname\n");
|
||
$tmpname = tempnam(session_save_path(), 'DataObject_');
|
||
|
||
$fh = fopen($tmpname, "w");
|
||
if (!$fh) {
|
||
return PEAR::raiseError(
|
||
"Failed to create temporary file: $tmpname\n".
|
||
"make sure session.save_path is set and is writable\n",
|
||
null,
|
||
PEAR_ERROR_DIE
|
||
);
|
||
}
|
||
fputs($fh, $out);
|
||
fclose($fh);
|
||
$perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
|
||
|
||
// windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
|
||
if (!@rename($tmpname, $outfilename)) {
|
||
unlink($outfilename);
|
||
rename($tmpname, $outfilename);
|
||
}
|
||
|
||
chmod($outfilename, $perms);
|
||
}
|
||
//echo $out;
|
||
}
|
||
|
||
/**
|
||
* class being extended (can be overridden by [DB_DataObject] extends=xxxx
|
||
*
|
||
* @var string
|
||
* @access private
|
||
*/
|
||
public $_extends = 'DB_DataObject';
|
||
|
||
/**
|
||
* line to use for require('DB/DataObject.php');
|
||
*
|
||
* @var string
|
||
* @access private
|
||
*/
|
||
public $_extendsFile = "DB/DataObject.php";
|
||
|
||
/**
|
||
* class being generated
|
||
*
|
||
* @var string
|
||
* @access private
|
||
*/
|
||
public $_className;
|
||
|
||
/**
|
||
* The table class geneation part - single file.
|
||
*
|
||
* @access private
|
||
* @return none
|
||
*/
|
||
public function _generateClassTable($input = '')
|
||
{
|
||
// title = expand me!
|
||
$foot = "";
|
||
$head = "<?php\n/**\n * Table Definition for {$this->table}\n";
|
||
$head .= $this->derivedHookPageLevelDocBlock();
|
||
$head .= " */\n";
|
||
$head .= $this->derivedHookExtendsDocBlock();
|
||
|
||
|
||
// requires - if you set extends_location = (blank) then no require line will be set
|
||
// this can be used if you have an autoloader
|
||
|
||
if (!empty($this->_extendsFile)) {
|
||
$head .= "require_once '{$this->_extendsFile}';\n\n";
|
||
}
|
||
// add dummy class header in...
|
||
// class
|
||
$head .= $this->derivedHookClassDocBlock();
|
||
$head .= "class {$this->classname} extends {$this->_extends} \n{";
|
||
|
||
$body = "\n ###START_AUTOCODE\n";
|
||
$body .= " /* the code below is auto generated do not remove the above tag */\n\n";
|
||
// table
|
||
|
||
$p = str_repeat(' ', max(2, (18 - strlen($this->table)))) ;
|
||
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
|
||
|
||
$var = (substr(phpversion(), 0, 1) > 4) ? 'public' : 'var';
|
||
$var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
|
||
|
||
|
||
$body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
|
||
|
||
// if we are using the option database_{databasename} = dsn
|
||
// then we should add var $_database = here
|
||
// as database names may not always match..
|
||
|
||
if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) {
|
||
DB_DataObject::_loadConfig();
|
||
}
|
||
|
||
// Only include the $_database property if the omit_database_var is unset or false
|
||
|
||
if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
|
||
$p = str_repeat(' ', max(2, (16 - strlen($this->_database))));
|
||
$body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
|
||
}
|
||
|
||
|
||
if (!empty($options['generator_novars'])) {
|
||
$var = '//'.$var;
|
||
}
|
||
|
||
$defs = $this->_definitions[$this->table];
|
||
|
||
// show nice information!
|
||
$connections = array();
|
||
$sets = array();
|
||
|
||
foreach ($defs as $t) {
|
||
if (!strlen(trim($t->name))) {
|
||
continue;
|
||
}
|
||
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
|
||
echo "*****************************************************************\n".
|
||
"** WARNING COLUMN NAME UNUSABLE **\n".
|
||
"** Found column '{$t->name}', of type '{$t->type}' **\n".
|
||
"** Since this column name can't be converted to a php variable **\n".
|
||
"** name, and the whole idea of mapping would result in a mess **\n".
|
||
"** This column has been ignored... **\n".
|
||
"*****************************************************************\n";
|
||
continue;
|
||
}
|
||
|
||
$pad = str_repeat(' ', max(2, (30 - strlen($t->name))));
|
||
|
||
$length = empty($t->len) ? '' : '('.$t->len.')';
|
||
$flags = strlen($t->flags) ? (' '. trim($t->flags)) : '';
|
||
$body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n";
|
||
|
||
// can not do set as PEAR::DB table info doesnt support it.
|
||
//if (substr($t->Type,0,3) == "set")
|
||
// $sets[$t->Field] = "array".substr($t->Type,3);
|
||
$body .= $this->derivedHookVar($t, strlen($p));
|
||
}
|
||
|
||
$body .= $this->derivedHookPostVar($defs);
|
||
|
||
// THIS IS TOTALLY BORKED old FC creation
|
||
// IT WILL BE REMOVED!!!!! in DataObjects 1.6
|
||
// grep -r __clone * to find all it's uses
|
||
// and replace them with $x = clone($y);
|
||
// due to the change in the PHP5 clone design.
|
||
$static = 'static';
|
||
if (substr(phpversion(), 0, 1) < 5) {
|
||
$body .= "\n";
|
||
$body .= " /* ZE2 compatibility trick*/\n";
|
||
$body .= " function __clone() { return \$this;}\n";
|
||
}
|
||
|
||
|
||
// depricated - in here for BC...
|
||
if (!empty($options['static_get'])) {
|
||
|
||
// simple creation tools ! (static stuff!)
|
||
$body .= "\n";
|
||
$body .= " /* Static get */\n";
|
||
$body .= " $static function staticGet(\$k,\$v=NULL) { " .
|
||
"return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n";
|
||
}
|
||
// generate getter and setter methods
|
||
$body .= $this->_generateGetters($input);
|
||
$body .= $this->_generateSetters($input);
|
||
$body .= $this->_generateLinkMethods($input);
|
||
/*
|
||
theoretically there is scope here to introduce 'list' methods
|
||
based up 'xxxx_up' column!!! for heiracitcal trees..
|
||
*/
|
||
|
||
// set methods
|
||
//foreach ($sets as $k=>$v) {
|
||
// $kk = strtoupper($k);
|
||
// $body .=" function getSets{$k}() { return {$v}; }\n";
|
||
//}
|
||
|
||
if (!empty($options['generator_no_ini'])) {
|
||
$def = $this->_generateDefinitionsTable(); // simplify this!?
|
||
$body .= $this->_generateTableFunction($def['table']);
|
||
$body .= $this->_generateKeysFunction($def['keys']);
|
||
$body .= $this->_generateSequenceKeyFunction($def);
|
||
$body .= $this->_generateDefaultsFunction($this->table, $def['table']);
|
||
} elseif (!empty($options['generator_add_defaults'])) {
|
||
// I dont really like doing it this way (adding another option)
|
||
// but it helps on older projects.
|
||
$def = $this->_generateDefinitionsTable(); // simplify this!?
|
||
$body .= $this->_generateDefaultsFunction($this->table, $def['table']);
|
||
}
|
||
$body .= $this->derivedHookFunctions($input);
|
||
|
||
$body .= "\n /* the code above is auto generated do not remove the tag below */";
|
||
$body .= "\n ###END_AUTOCODE\n";
|
||
|
||
|
||
// stubs..
|
||
|
||
if (!empty($options['generator_add_validate_stubs'])) {
|
||
foreach ($defs as $t) {
|
||
if (!strlen(trim($t->name))) {
|
||
continue;
|
||
}
|
||
$validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
|
||
// dont re-add it..
|
||
if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
|
||
continue;
|
||
}
|
||
$body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
$foot .= "}\n";
|
||
$full = $head . $body . $foot;
|
||
|
||
if (!$input) {
|
||
return $full;
|
||
}
|
||
if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s', $input)) {
|
||
return $full;
|
||
}
|
||
if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', $input)) {
|
||
return $full;
|
||
}
|
||
|
||
|
||
/* this will only replace extends DB_DataObject by default,
|
||
unless use set generator_class_rewrite to ANY or a name*/
|
||
|
||
$class_rewrite = 'DB_DataObject';
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
|
||
$class_rewrite = 'DB_DataObject';
|
||
}
|
||
if ($class_rewrite == 'ANY') {
|
||
$class_rewrite = '[a-z_]+';
|
||
}
|
||
|
||
$input = preg_replace(
|
||
'/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
|
||
"\nclass {$this->classname} extends {$this->_extends} \n{\n",
|
||
$input
|
||
);
|
||
|
||
$ret = preg_replace(
|
||
'/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
|
||
$body,
|
||
$input
|
||
);
|
||
|
||
if (!strlen($ret)) {
|
||
return PEAR::raiseError(
|
||
"PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
|
||
"pcre.backtrack_limit=1000000\n".
|
||
"pcre.recursion_limit=1000000\n",
|
||
null,
|
||
PEAR_ERROR_DIE
|
||
);
|
||
}
|
||
|
||
return $ret;
|
||
}
|
||
|
||
/**
|
||
* hook to add extra methods to all classes
|
||
*
|
||
* called once for each class, use with $this->table and
|
||
* $this->_definitions[$this->table], to get data out of the current table,
|
||
* use it to add extra methods to the default classes.
|
||
*
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookFunctions($input = "")
|
||
{
|
||
// This is so derived generator classes can generate functions
|
||
// It MUST NOT be changed here!!!
|
||
return "";
|
||
}
|
||
|
||
/**
|
||
* hook for var lines
|
||
* called each time a var line is generated, override to add extra var
|
||
* lines
|
||
*
|
||
* @param object t containing type,len,flags etc. from tableInfo call
|
||
* @param int padding number of spaces
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookVar(&$t, $padding)
|
||
{
|
||
// This is so derived generator classes can generate variabels
|
||
// It MUST NOT be changed here!!!
|
||
return "";
|
||
}
|
||
/**
|
||
* hook for after var lines (
|
||
* called at the end of the output of var line have generated, override to add extra var
|
||
* lines
|
||
*
|
||
* @param array cols containing array of objects with type,len,flags etc. from tableInfo call
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookPostVar($t)
|
||
{
|
||
// This is so derived generator classes can generate variabels
|
||
// It MUST NOT be changed here!!!
|
||
return "";
|
||
}
|
||
/**
|
||
* hook to add extra page-level (in terms of phpDocumentor) DocBlock
|
||
*
|
||
* called once for each class, use it add extra page-level docs
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookPageLevelDocBlock()
|
||
{
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* hook to add extra doc block (in terms of phpDocumentor) to extend string
|
||
*
|
||
* called once for each class, use it add extra comments to extends
|
||
* string (require_once...)
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookExtendsDocBlock()
|
||
{
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* hook to add extra class level DocBlock (in terms of phpDocumentor)
|
||
*
|
||
* called once for each class, use it add extra comments to class
|
||
* string (require_once...)
|
||
* @access public
|
||
* @return string added to class eg. functions.
|
||
*/
|
||
public function derivedHookClassDocBlock()
|
||
{
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
|
||
/**
|
||
* getProxyFull - create a class definition on the fly and instantate it..
|
||
*
|
||
* similar to generated files - but also evals the class definitoin code.
|
||
*
|
||
*
|
||
* @param string database name
|
||
* @param string table name of table to create proxy for.
|
||
*
|
||
*
|
||
* @return object Instance of class. or PEAR Error
|
||
* @access public
|
||
*/
|
||
public function getProxyFull($database, $table)
|
||
{
|
||
if ($err = $this->fillTableSchema($database, $table)) {
|
||
return $err;
|
||
}
|
||
|
||
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
|
||
|
||
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
|
||
$this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
|
||
|
||
$classname = $this->classname = $this->getClassNameFromTableName($this->table);
|
||
|
||
$out = $this->_generateClassTable();
|
||
//echo $out;
|
||
eval('?>'.$out);
|
||
return new $classname;
|
||
}
|
||
|
||
/**
|
||
* fillTableSchema - set the database schema on the fly
|
||
*
|
||
*
|
||
*
|
||
* @param string database name
|
||
* @param string table name of table to create schema info for
|
||
*
|
||
* @return none | PEAR::error()
|
||
* @access public
|
||
*/
|
||
public function fillTableSchema($database, $table)
|
||
{
|
||
global $_DB_DATAOBJECT;
|
||
// a little bit of sanity testing.
|
||
if ((false !== strpos($database, "'")) || (false !== strpos($database, ";"))) {
|
||
return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
|
||
}
|
||
|
||
$this->_database = $database;
|
||
|
||
$this->_connect();
|
||
$table = trim($table);
|
||
|
||
// a little bit of sanity testing.
|
||
if ((false !== strpos($table, "'")) || (false !== strpos($table, ";"))) {
|
||
return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
|
||
}
|
||
$__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
|
||
|
||
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
|
||
$is_MDB2 = ($db_driver != 'DB') ? true : false;
|
||
|
||
if (!$is_MDB2) {
|
||
// try getting a list of schema tables first. (postgres)
|
||
$__DB->expectError(DB_ERROR_UNSUPPORTED);
|
||
$this->tables = $__DB->getListOf('schema.tables');
|
||
$__DB->popExpect();
|
||
} else {
|
||
/**
|
||
* set portability and some modules to fetch the informations
|
||
*/
|
||
$__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
|
||
$__DB->loadModule('Manager');
|
||
$__DB->loadModule('Reverse');
|
||
}
|
||
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
|
||
$__DB->quoteIdentifier($table) : $table;
|
||
|
||
if (!$is_MDB2) {
|
||
$defs = $__DB->tableInfo($quotedTable);
|
||
} else {
|
||
$defs = $__DB->reverse->tableInfo($quotedTable);
|
||
if (PEAR::isError($defs)) {
|
||
return $defs;
|
||
}
|
||
foreach ($defs as $k => $v) {
|
||
if (!isset($defs[$k]['length'])) {
|
||
continue;
|
||
}
|
||
$defs[$k]['len'] = $defs[$k]['length'];
|
||
}
|
||
}
|
||
|
||
if (PEAR::isError($defs)) {
|
||
return $defs;
|
||
}
|
||
|
||
|
||
|
||
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
|
||
$this->debug("getting def for $database/$table", 'fillTable');
|
||
$this->debug(print_r($defs, true), 'defs');
|
||
}
|
||
// cast all definitions to objects - as we deal with that better.
|
||
|
||
|
||
foreach ($defs as $def) {
|
||
if (is_array($def)) {
|
||
$this->_definitions[$table][] = (object) $def;
|
||
}
|
||
}
|
||
|
||
$this->table = trim($table);
|
||
$ret = $this->_generateDefinitionsTable();
|
||
|
||
$_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
|
||
$_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Generate getter methods for class definition
|
||
*
|
||
* @param string $input Existing class contents
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateGetters($input)
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$getters = '';
|
||
|
||
// only generate if option is set to true
|
||
if (empty($options['generate_getters'])) {
|
||
return '';
|
||
}
|
||
|
||
// remove auto-generated code from input to be able to check if the method exists outside of the auto-code
|
||
$input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
|
||
|
||
$getters .= "\n\n";
|
||
$defs = $this->_definitions[$this->table];
|
||
|
||
// loop through properties and create getter methods
|
||
foreach ($defs = $defs as $t) {
|
||
|
||
// build mehtod name
|
||
$methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
|
||
|
||
if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
|
||
continue;
|
||
}
|
||
|
||
$getters .= " /**\n";
|
||
$getters .= " * Getter for \${$t->name}\n";
|
||
$getters .= " *\n";
|
||
$getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
|
||
: " * @return {$t->type}\n";
|
||
$getters .= " * @access public\n";
|
||
$getters .= " */\n";
|
||
$getters .= (substr(phpversion(), 0, 1) > 4) ? ' public '
|
||
: ' ';
|
||
$getters .= "function $methodName() {\n";
|
||
$getters .= " return \$this->{$t->name};\n";
|
||
$getters .= " }\n\n";
|
||
}
|
||
|
||
|
||
return $getters;
|
||
}
|
||
/**
|
||
* Generate link setter/getter methods for class definition
|
||
*
|
||
* @param string Existing class contents
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateLinkMethods($input)
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$setters = '';
|
||
|
||
// only generate if option is set to true
|
||
|
||
// generate_link_methods true::
|
||
|
||
|
||
if (empty($options['generate_link_methods'])) {
|
||
//echo "skip lm? - not set";
|
||
return '';
|
||
}
|
||
|
||
if (empty($this->_fkeys)) {
|
||
// echo "skip lm? - fkyes empty";
|
||
return '';
|
||
}
|
||
if (empty($this->_fkeys[$this->table])) {
|
||
//echo "skip lm? - no fkeys for {$this->table}";
|
||
return '';
|
||
}
|
||
|
||
// remove auto-generated code from input to be able to check if the method exists outside of the auto-code
|
||
$input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
|
||
|
||
$setters .= "\n";
|
||
$defs = $this->_fkeys[$this->table];
|
||
|
||
|
||
// $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
|
||
|
||
// loop through properties and create setter methods
|
||
foreach ($defs as $k => $info) {
|
||
|
||
// build mehtod name
|
||
$methodName = is_callable($options['generate_link_methods']) ?
|
||
$options['generate_link_methods']($k) : $k;
|
||
|
||
if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
|
||
continue;
|
||
}
|
||
|
||
$setters .= " /**\n";
|
||
$setters .= " * Getter / Setter for \${$k}\n";
|
||
$setters .= " *\n";
|
||
$setters .= " * @param mixed (optional) value to assign\n";
|
||
$setters .= " * @access public\n";
|
||
|
||
$setters .= " */\n";
|
||
$setters .= (substr(phpversion(), 0, 1) > 4) ? ' public '
|
||
: ' ';
|
||
$setters .= "function $methodName() {\n";
|
||
$setters .= " return \$this->link('$k', func_get_args());\n";
|
||
$setters .= " }\n\n";
|
||
}
|
||
|
||
return $setters;
|
||
}
|
||
|
||
/**
|
||
* Generate setter methods for class definition
|
||
*
|
||
* @param string Existing class contents
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateSetters($input)
|
||
{
|
||
$options = &PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$setters = '';
|
||
|
||
// only generate if option is set to true
|
||
if (empty($options['generate_setters'])) {
|
||
return '';
|
||
}
|
||
|
||
// remove auto-generated code from input to be able to check if the method exists outside of the auto-code
|
||
$input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
|
||
|
||
$setters .= "\n";
|
||
$defs = $this->_definitions[$this->table];
|
||
|
||
// loop through properties and create setter methods
|
||
foreach ($defs = $defs as $t) {
|
||
|
||
// build mehtod name
|
||
$methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
|
||
|
||
if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
|
||
continue;
|
||
}
|
||
|
||
$setters .= " /**\n";
|
||
$setters .= " * Setter for \${$t->name}\n";
|
||
$setters .= " *\n";
|
||
$setters .= " * @param mixed input value\n";
|
||
$setters .= " * @access public\n";
|
||
$setters .= " */\n";
|
||
$setters .= (substr(phpversion(), 0, 1) > 4) ? ' public '
|
||
: ' ';
|
||
$setters .= "function $methodName(\$value) {\n";
|
||
$setters .= " \$this->{$t->name} = \$value;\n";
|
||
$setters .= " }\n\n";
|
||
}
|
||
|
||
|
||
return $setters;
|
||
}
|
||
/**
|
||
* Generate table Function - used when generator_no_ini is set.
|
||
*
|
||
* @param array table array.
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateTableFunction($def)
|
||
{
|
||
$defines = explode(',', 'INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
|
||
|
||
$ret = "\n" .
|
||
" function table()\n" .
|
||
" {\n" .
|
||
" return array(\n";
|
||
|
||
foreach ($def as $k=>$v) {
|
||
$str = '0';
|
||
foreach ($defines as $dn) {
|
||
if ($v & constant('DB_DATAOBJECT_' . $dn)) {
|
||
$str .= ' + DB_DATAOBJECT_' . $dn;
|
||
}
|
||
}
|
||
if (strlen($str) > 1) {
|
||
$str = substr($str, 3); // strip the 0 +
|
||
}
|
||
// hopefully addslashes is good enough here!!!
|
||
$ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
|
||
}
|
||
return $ret . " );\n" .
|
||
" }\n";
|
||
}
|
||
/**
|
||
* Generate keys Function - used generator_no_ini is set.
|
||
*
|
||
* @param array keys array.
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateKeysFunction($def)
|
||
{
|
||
$ret = "\n" .
|
||
" function keys()\n" .
|
||
" {\n" .
|
||
" return array(";
|
||
|
||
foreach ($def as $k=>$type) {
|
||
// hopefully addslashes is good enough here!!!
|
||
$ret .= '\''.addslashes($k).'\', ';
|
||
}
|
||
$ret = preg_replace('#, $#', '', $ret);
|
||
return $ret . ");\n" .
|
||
" }\n";
|
||
}
|
||
/**
|
||
* Generate sequenceKey Function - used generator_no_ini is set.
|
||
*
|
||
* @param array table and key definition.
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateSequenceKeyFunction($def)
|
||
{
|
||
|
||
//print_r($def);
|
||
// DB_DataObject::debugLevel(5);
|
||
global $_DB_DATAOBJECT;
|
||
// print_r($def);
|
||
|
||
|
||
$dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
|
||
$realkeys = $def['keys'];
|
||
$keys = array_keys($realkeys);
|
||
$usekey = isset($keys[0]) ? $keys[0] : false;
|
||
$table = $def['table'];
|
||
|
||
|
||
$seqname = false;
|
||
|
||
|
||
|
||
|
||
$ar = array(false,false,false);
|
||
if ($usekey !== false) {
|
||
if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
|
||
$usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
|
||
if (strpos($usekey, ':') !== false) {
|
||
list($usekey, $seqname) = explode(':', $usekey);
|
||
}
|
||
}
|
||
|
||
if (in_array($dbtype, array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
|
||
($table[$usekey] & DB_DATAOBJECT_INT) &&
|
||
isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
|
||
) {
|
||
// use native sequence keys.
|
||
$ar = array($usekey,true,$seqname);
|
||
} else {
|
||
// use generated sequence keys..
|
||
if ($table[$usekey] & DB_DATAOBJECT_INT) {
|
||
$ar = array($usekey,false,$seqname);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
$ret = "\n" .
|
||
" function sequenceKey() // keyname, use native, native name\n" .
|
||
" {\n" .
|
||
" return array(";
|
||
foreach ($ar as $v) {
|
||
switch (gettype($v)) {
|
||
case 'boolean':
|
||
$ret .= ($v ? 'true' : 'false') . ', ';
|
||
break;
|
||
|
||
case 'string':
|
||
$ret .= "'" . $v . "', ";
|
||
break;
|
||
|
||
default: // eak
|
||
$ret .= "null, ";
|
||
|
||
}
|
||
}
|
||
$ret = preg_replace('#, $#', '', $ret);
|
||
return $ret . ");\n" .
|
||
" }\n";
|
||
}
|
||
/**
|
||
* Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
|
||
* Only supports mysql and mysqli ... welcome ideas for more..
|
||
*
|
||
*
|
||
* @param array table and key definition.
|
||
* @return string
|
||
* @access public
|
||
*/
|
||
public function _generateDefaultsFunction($table, $defs)
|
||
{
|
||
$__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
|
||
if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
|
||
return; // cant handle non-mysql introspection for defaults.
|
||
}
|
||
$options = PEAR::getStaticProperty('DB_DataObject', 'options');
|
||
$db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
|
||
$method = $db_driver == 'DB' ? 'getAll' : 'queryAll';
|
||
$res = $__DB->$method('DESCRIBE ' . $table, DB_FETCHMODE_ASSOC);
|
||
$defaults = array();
|
||
foreach ($res as $ar) {
|
||
// this is initially very dumb... -> and it may mess up..
|
||
$type = $defs[$ar['Field']];
|
||
|
||
switch (true) {
|
||
|
||
case (is_null($ar['Default'])):
|
||
$defaults[$ar['Field']] = 'null';
|
||
break;
|
||
|
||
case ($type & DB_DATAOBJECT_DATE):
|
||
case ($type & DB_DATAOBJECT_TIME):
|
||
case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
|
||
break;
|
||
|
||
case ($type & DB_DATAOBJECT_BOOL):
|
||
$defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
|
||
break;
|
||
|
||
|
||
case ($type & DB_DATAOBJECT_STR):
|
||
$defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
|
||
break;
|
||
|
||
|
||
default: // hopefully eveything else... - numbers etc.
|
||
if (!strlen($ar['Default'])) {
|
||
continue;
|
||
}
|
||
if (is_numeric($ar['Default'])) {
|
||
$defaults[$ar['Field']] = $ar['Default'];
|
||
}
|
||
break;
|
||
|
||
}
|
||
//var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
|
||
}
|
||
if (empty($defaults)) {
|
||
return;
|
||
}
|
||
|
||
$ret = "\n" .
|
||
" function defaults() // column default values \n" .
|
||
" {\n" .
|
||
" return array(\n";
|
||
foreach ($defaults as $k=>$v) {
|
||
$ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
|
||
}
|
||
return $ret . " );\n" .
|
||
" }\n";
|
||
}
|
||
}
|