From 14c9366aac697e87499c5748b36fa7bf5e6cd320 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 22 Aug 2008 09:17:14 -0400 Subject: include external libs in a subdir to make install easier darcs-hash:20080822131714-84dde-6978424ded2ed1041a65142a25560654ac717fcd.gz --- extlib/DB/DataObject/Generator.php | 1553 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1553 insertions(+) create mode 100644 extlib/DB/DataObject/Generator.php (limited to 'extlib/DB/DataObject/Generator.php') diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php new file mode 100644 index 000000000..de16af692 --- /dev/null +++ b/extlib/DB/DataObject/Generator.php @@ -0,0 +1,1553 @@ + + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Generator.php,v 1.141 2008/01/30 02:29:39 alan_k Exp $ + * @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_Generator] + * ; 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 + */ + var $tables; + + /** + * associative array table -> array of table row objects + * + * @var array + * @access private + */ + var $_definitions; + + /** + * active table being output + * + * @var string + * @access private + */ + var $table; // active tablename + + + /** + * The 'starter' = call this to start the process + * + * @access public + * @return none + */ + 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(); + + 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 + */ + var $_newConfig; + + /** + * Build a list of tables; + * and store it in $this->tables and $this->_definitions[tablename]; + * + * @access private + * @return none + */ + 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_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->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); + $__DB->loadModule('Manager'); + $__DB->loadModule('Reverse'); + } + + if ((empty($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_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('views'); + } else { + $views = $__DB->manager->listViews(); + } + if (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)) { + continue; + } else if (isset($options['generator_exclude_regex']) && + preg_match($options['generator_exclude_regex'],$table)) { + continue; + } + // 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]; + } + } + $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. + foreach ($defs as $k => $v) { + if (!isset($defs[$k]['length'])) { + continue; + } + $defs[$k]['len'] = $defs[$k]['length']; + } + } + + if (is_a($defs,'PEAR_Error')) { + // running in debug mode should pick this up as a big warning.. + $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; + } + + $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 + */ + 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'); + 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); + // } + } + + /** + * generate Foreign Keys (for links.ini) + * Currenly only works with mysql / mysqli + * to use, you must set option: generate_links=true + * + * @author Pascal Schöni + */ + 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'))) { + echo "WARNING: cant handle non-mysql introspection for defaults."; + return; // cant handle non-mysql introspection for defaults. + } + + $DB = $this->getDatabaseConnection(); + + $fk = array(); + + foreach($this->tables as $this->table) { + $res =& $DB->query('SHOW CREATE TABLE ' . $this->table); + 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) < 1) { + continue; + } + for ($i = 0; $i < count($treffer); $i++) { + $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3]; + } + + } + + $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'])) { + return; + } + + + $file = "{$options['schema_location']}/{$this->_database}.links.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); // not sure why this is needed? + $tmpname = tempnam(session_save_path(),'DataObject_'); + + $fh = fopen($tmpname,'w'); + 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. + */ + function _generateDefinitionsTable() + { + global $_DB_DATAOBJECT; + + $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 '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': + + $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 'TINYBLOB': + case 'BLOB': /// these should really be ignored!!!??? + case 'MEDIUMBLOB': + case 'LONGBLOB': + 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"; + if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags)) + || (isset($t->autoincrement) && ($t->autoincrement === true))) { + + // native sequences = 2 + if ($write_ini) { + $keys_out_primary .= "{$t->name} = N\n"; + } + $ret_keys_primary[$t->name] = 'N'; + + } else if (preg_match("/(primary|unique)/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; + */ + + + 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; + */ + + + 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; + */ + + + function getMethodNameFromColumnName($col) + { + return ucfirst($col); + } + + + + + /* + * building the class files + * for each of the tables output a file! + */ + function generateClasses() + { + //echo "Generating Class files: \n"; + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + + + if ($extends = @$options['extends']) { + $this->_extends = $extends; + $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"); + 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_Generator] extends=xxxx + * + * @var string + * @access private + */ + var $_extends = 'DB_DataObject'; + + /** + * line to use for require('DB/DataObject.php'); + * + * @var string + * @access private + */ + var $_extendsFile = "DB/DataObject.php"; + + /** + * class being generated + * + * @var string + * @access private + */ + var $_className; + + /** + * The table class geneation part - single file. + * + * @access private + * @return none + */ + function _generateClassTable($input = '') + { + // title = expand me! + $foot = ""; + $head = "table}\n"; + $head .= $this->derivedHookPageLevelDocBlock(); + $head .= " */\n"; + $head .= $this->derivedHookExtendsDocBlock(); + + + // requires + $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 + $padding = (30 - strlen($this->table)); + $padding = ($padding < 2) ? 2 : $padding; + + $p = str_repeat(' ',$padding) ; + + $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 (isset($options["database_{$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; + } + + + $padding = (30 - strlen($t->name)); + if ($padding < 2) $padding =2; + $p = str_repeat(' ',$padding) ; + + $body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->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,$padding); + } + + // 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. + + if ( substr(phpversion(),0,1) < 5) { + $body .= "\n"; + $body .= " /* ZE2 compatibility trick*/\n"; + $body .= " function __clone() { return \$this;}\n"; + } + + // simple creation tools ! (static stuff!) + $body .= "\n"; + $body .= " /* Static get */\n"; + $body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n"; + + // generate getter and setter methods + $body .= $this->_generateGetters($input); + $body .= $this->_generateSetters($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']); + } else if (!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. + */ + 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. + */ + function derivedHookVar(&$t,$padding) + { + // 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. + */ + 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. + */ + 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. + */ + 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 + */ + 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']; + + if ($extends = @$options['extends']) { + $this->_extends = $extends; + $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 + */ + 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']) ? + $__DB->quoteIdentifier($table) : $table; + + if (!$is_MDB2) { + $defs = $__DB->tableInfo($quotedTable); + } else { + $defs = $__DB->reverse->tableInfo($quotedTable); + 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 + */ + 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 setter methods for class definition + * + * @param string Existing class contents + * @return string + * @access 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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"; + + + + + } + + + + + +} -- cgit v1.2.3-54-g00ecf