3 * Generation tools for DB_DataObject
7 * LICENSE: This source file is subject to version 3.0 of the PHP license
8 * that is available through the world-wide-web at the following URI:
9 * http://www.php.net/license/3_0.txt. If you did not receive a copy of
10 * the PHP License and are unable to obtain it through the web, please
11 * send a note to license@php.net so we can mail you a copy immediately.
14 * @package DB_DataObject
15 * @author Alan Knowles <alan@akbkhome.com>
16 * @copyright 1997-2006 The PHP Group
17 * @license http://www.php.net/license/3_0.txt PHP License 3.0
18 * @version CVS: $Id: Generator.php,v 1.141 2008/01/30 02:29:39 alan_k Exp $
19 * @link http://pear.php.net/package/DB_DataObject
24 * This class uses eval to create classes on the fly.
25 * The table name and database name are used to check the database before writing the
26 * class definitions, we now check for quotes and semi-colon's in both variables
27 * so I cant see how it would be possible to generate code even if
28 * for some crazy reason you took the classname and table name from User Input.
30 * If you consider that wrong, or can prove it.. let me know!
36 * [DB_DataObject_Generator]
37 * ; optional default = DB/DataObject.php
39 * ; optional default = DB_DataObject
41 * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
42 * generator_class_rewrite = ANY|specific_name // default is DB_DataObject
48 * We lazy load here, due to problems with the tests not setting up include path correctly.
51 class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
52 //require_once('Config.php');
57 * @package DB_DataObject
59 class DB_DataObject_Generator extends DB_DataObject
61 /* =========================================================== */
62 /* Utility functions - for building db config files */
63 /* =========================================================== */
66 * Array of table names
74 * associative array table -> array of table row objects
82 * active table being output
87 var $table; // active tablename
91 * The 'starter' = call this to start the process
98 $options = &PEAR::getStaticProperty('DB_DataObject','options');
99 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
101 $databases = array();
102 foreach($options as $k=>$v) {
103 if (substr($k,0,9) == 'database_') {
104 $databases[substr($k,9)] = $v;
108 if (isset($options['database'])) {
109 if ($db_driver == 'DB') {
110 require_once 'DB.php';
111 $dsn = DB::parseDSN($options['database']);
113 require_once 'MDB2.php';
114 $dsn = MDB2::parseDSN($options['database']);
117 if (!isset($database[$dsn['database']])) {
118 $databases[$dsn['database']] = $options['database'];
122 foreach($databases as $databasename => $database) {
126 $this->debug("CREATING FOR $databasename\n");
127 $class = get_class($this);
129 $t->_database_dsn = $database;
132 $t->_database = $databasename;
133 if ($db_driver == 'DB') {
134 require_once 'DB.php';
135 $dsn = DB::parseDSN($database);
137 require_once 'MDB2.php';
138 $dsn = MDB2::parseDSN($database);
141 if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
142 $t->_database = basename($t->_database);
144 $t->_createTableList();
146 foreach(get_class_methods($class) as $method) {
147 if (substr($method,0,8 ) != 'generate') {
150 $this->debug("calling $method");
154 $this->debug("DONE\n\n");
158 * Output File was config object, now just string
159 * Used to generate the Tables
161 * @var string outputbuffer for table definitions
167 * Build a list of tables;
168 * and store it in $this->tables and $this->_definitions[tablename];
173 function _createTableList()
176 $options = &PEAR::getStaticProperty('DB_DataObject','options');
178 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
180 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
181 $is_MDB2 = ($db_driver != 'DB') ? true : false;
183 if (is_a($__DB , 'PEAR_Error')) {
184 return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
188 // try getting a list of schema tables first. (postgres)
189 $__DB->expectError(DB_ERROR_UNSUPPORTED);
190 $this->tables = $__DB->getListOf('schema.tables');
194 * set portability and some modules to fetch the informations
196 $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
197 $__DB->loadModule('Manager');
198 $__DB->loadModule('Reverse');
201 if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) {
202 //if that fails fall back to clasic tables list.
204 // try getting a list of schema tables first. (postgres)
205 $__DB->expectError(DB_ERROR_UNSUPPORTED);
206 $this->tables = $__DB->getListOf('tables');
209 $this->tables = $__DB->manager->listTables();
210 $sequences = $__DB->manager->listSequences();
211 foreach ($sequences as $k => $v) {
212 $this->tables[] = $__DB->getSequenceName($v);
217 if (is_a($this->tables , 'PEAR_Error')) {
218 return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
221 // build views as well if asked to.
222 if (!empty($options['build_views'])) {
224 $views = $__DB->getListOf('views');
226 $views = $__DB->manager->listViews();
228 if (is_a($views,'PEAR_Error')) {
229 return PEAR::raiseError(
230 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
236 $this->tables = array_merge ($this->tables, $views);
239 // declare a temporary table to be filled with matching tables names
240 $tmp_table = array();
243 foreach($this->tables as $table) {
244 if (isset($options['generator_include_regex']) &&
245 !preg_match($options['generator_include_regex'],$table)) {
247 } else if (isset($options['generator_exclude_regex']) &&
248 preg_match($options['generator_exclude_regex'],$table)) {
251 // postgres strip the schema bit from the
252 if (!empty($options['generator_strip_schema'])) {
253 $bits = explode('.', $table,2);
255 if (count($bits) > 1) {
259 $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
260 $__DB->quoteIdentifier($table) : $table;
264 $defs = $__DB->tableInfo($quotedTable);
266 $defs = $__DB->reverse->tableInfo($quotedTable);
267 // rename the length value, so it matches db's return.
268 foreach ($defs as $k => $v) {
269 if (!isset($defs[$k]['length'])) {
272 $defs[$k]['len'] = $defs[$k]['length'];
276 if (is_a($defs,'PEAR_Error')) {
277 // running in debug mode should pick this up as a big warning..
278 $this->raiseError('Error reading tableInfo, '. $defs->toString());
281 // cast all definitions to objects - as we deal with that better.
285 foreach($defs as $def) {
286 if (!is_array($def)) {
290 $this->_definitions[$table][] = (object) $def;
293 // we find a matching table, just store it into a temporary array
294 $tmp_table[] = $table;
298 // the temporary table array is now the right one (tables names matching
299 // with regex expressions have been removed)
300 $this->tables = $tmp_table;
301 //print_r($this->_definitions);
305 * Auto generation of table data.
307 * it will output to db_oo_{database} the table definitions
312 function generateDefinitions()
314 $this->debug("Generating Definitions file: ");
315 if (!$this->tables) {
316 $this->debug("-- NO TABLES -- \n");
320 $options = &PEAR::getStaticProperty('DB_DataObject','options');
323 //$this->_newConfig = new Config('IniFile');
324 $this->_newConfig = '';
325 foreach($this->tables as $this->table) {
326 $this->_generateDefinitionsTable();
329 // dont generate a schema if location is not set
330 // it's created on the fly!
331 if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
334 if (!empty($options['generator_no_ini'])) { // built in ini files..
337 $base = @$options['schema_location'];
338 if (isset($options["ini_{$this->_database}"])) {
339 $file = $options["ini_{$this->_database}"];
341 $file = "{$base}/{$this->_database}.ini";
344 if (!file_exists(dirname($file))) {
345 require_once 'System.php';
346 System::mkdir(array('-p','-m',0755,dirname($file)));
348 $this->debug("Writing ini as {$file}\n");
350 $tmpname = tempnam(session_save_path(),'DataObject_');
351 //print_r($this->_newConfig);
352 $fh = fopen($tmpname,'w');
353 fwrite($fh,$this->_newConfig);
355 $perms = file_exists($file) ? fileperms($file) : 0755;
356 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
358 if (!@rename($tmpname, $file)) {
360 rename($tmpname, $file);
363 //$ret = $this->_newConfig->writeInput($file,false);
365 //if (PEAR::isError($ret) ) {
366 // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
371 * generate Foreign Keys (for links.ini)
372 * Currenly only works with mysql / mysqli
373 * to use, you must set option: generate_links=true
375 * @author Pascal Schöni
377 function generateForeignKeys()
379 $options = PEAR::getStaticProperty('DB_DataObject','options');
380 if (empty($options['generate_links'])) {
383 $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
384 if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
385 echo "WARNING: cant handle non-mysql introspection for defaults.";
386 return; // cant handle non-mysql introspection for defaults.
389 $DB = $this->getDatabaseConnection();
393 foreach($this->tables as $this->table) {
394 $res =& $DB->query('SHOW CREATE TABLE ' . $this->table);
395 if (PEAR::isError($res)) {
396 die($res->getMessage());
399 $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
401 // Extract FOREIGN KEYS
403 "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
408 if (count($treffer) < 1) {
411 for ($i = 0; $i < count($treffer); $i++) {
412 $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3];
419 foreach($fk as $table => $details) {
420 $links_ini .= "[$table]\n";
421 foreach ($details as $col => $ref) {
422 $links_ini .= "$col = $ref\n";
427 // dont generate a schema if location is not set
428 // it's created on the fly!
429 $options = PEAR::getStaticProperty('DB_DataObject','options');
431 if (empty($options['schema_location'])) {
436 $file = "{$options['schema_location']}/{$this->_database}.links.ini";
438 if (!file_exists(dirname($file))) {
439 require_once 'System.php';
440 System::mkdir(array('-p','-m',0755,dirname($file)));
443 $this->debug("Writing ini as {$file}\n");
445 //touch($file); // not sure why this is needed?
446 $tmpname = tempnam(session_save_path(),'DataObject_');
448 $fh = fopen($tmpname,'w');
449 fwrite($fh,$links_ini);
451 $perms = file_exists($file) ? fileperms($file) : 0755;
452 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
453 if (!@rename($tmpname, $file)) {
455 rename($tmpname, $file);
457 chmod($file, $perms);
462 * The table geneation part
465 * @return tabledef and keys array.
467 function _generateDefinitionsTable()
469 global $_DB_DATAOBJECT;
471 $defs = $this->_definitions[$this->table];
472 $this->_newConfig .= "\n[{$this->table}]\n";
473 $keys_out = "\n[{$this->table}__keys]\n";
474 $keys_out_primary = '';
475 $keys_out_secondary = '';
476 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
477 echo "TABLE STRUCTURE FOR {$this->table}\n";
480 $DB = $this->getDatabaseConnection();
481 $dbtype = $DB->phptype;
488 $ret_keys_primary = array();
489 $ret_keys_secondary = array();
493 foreach($defs as $t) {
499 switch (strtoupper($t->type)) {
502 case 'INT2': // postgres
503 case 'INT4': // postgres
504 case 'INT8': // postgres
505 case 'SERIAL4': // postgres
506 case 'SERIAL8': // postgres
512 $type = DB_DATAOBJECT_INT;
514 $type += DB_DATAOBJECT_BOOL;
520 case 'DOUBLE PRECISION': // double precision (firebird)
522 case 'FLOAT4': // real (postgres)
523 case 'FLOAT8': // double precision (postgres)
525 case 'MONEY': // mssql and maybe others
527 case 'NUMBER': // oci8
528 $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
532 $type = DB_DATAOBJECT_INT;
539 $type = DB_DATAOBJECT_BOOL;
540 // postgres needs to quote '0'
541 if ($dbtype == 'pgsql') {
542 $type += DB_DATAOBJECT_STR;
553 case 'SET': // not really but oh well
554 case 'TIMESTAMPTZ': // postgres
555 case 'BPCHAR': // postgres
556 case 'INTERVAL': // postgres (eg. '12 days')
558 case 'CIDR': // postgres IP net spec
559 case 'INET': // postgres IP
560 case 'MACADDR': // postgress network Mac address.
562 case 'INTEGER[]': // postgres type
563 case 'BOOLEAN[]': // postgres type
565 $type = DB_DATAOBJECT_STR;
572 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
577 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
581 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
587 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
590 case 'TIMESTAMP': // do other databases use this???
592 $type = ($dbtype == 'mysql') ?
593 DB_DATAOBJECT_MYSQLTIMESTAMP :
594 DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
599 case 'BLOB': /// these should really be ignored!!!???
602 case 'BYTEA': // postgres blob support..
603 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
606 echo "*****************************************************************\n".
607 "** WARNING UNKNOWN TYPE **\n".
608 "** Found column '{$t->name}', of type '{$t->type}' **\n".
609 "** Please submit a bug, describe what type you expect this **\n".
610 "** column to be **\n".
611 "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
612 "** Try using MDB2 as the backend - eg set the config option **\n".
613 "** db_driver = MDB2 **\n".
614 "*****************************************************************\n";
619 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
620 echo "*****************************************************************\n".
621 "** WARNING COLUMN NAME UNUSABLE **\n".
622 "** Found column '{$t->name}', of type '{$t->type}' **\n".
623 "** Since this column name can't be converted to a php variable **\n".
624 "** name, and the whole idea of mapping would result in a mess **\n".
625 "** This column has been ignored... **\n".
626 "*****************************************************************\n";
630 if (!strlen(trim($t->name))) {
631 continue; // is this a bug?
634 if (preg_match('/not[ _]null/i',$t->flags)) {
635 $type += DB_DATAOBJECT_NOTNULL;
639 if (in_array($t->name,array('null','yes','no','true','false'))) {
640 echo "*****************************************************************\n".
642 "** Found column '{$t->name}', which is invalid in an .ini file **\n".
643 "** This line will not be writen to the file - you will have **\n".
644 "** define the keys()/method manually. **\n".
645 "*****************************************************************\n";
648 $this->_newConfig .= "{$t->name} = $type\n";
651 $ret['table'][$t->name] = $type;
652 // i've no idea if this will work well on other databases?
653 // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
654 // if no keys exist fall back to using unique
655 //echo "\n{$t->name} => {$t->flags}\n";
656 if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags))
657 || (isset($t->autoincrement) && ($t->autoincrement === true))) {
659 // native sequences = 2
661 $keys_out_primary .= "{$t->name} = N\n";
663 $ret_keys_primary[$t->name] = 'N';
665 } else if (preg_match("/(primary|unique)/i",$t->flags)) {
668 if (!preg_match("/(primary)/i",$t->flags)) {
673 $keys_out_secondary .= "{$t->name} = {$key_type}\n";
675 $ret_keys_secondary[$t->name] = $key_type;
681 $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
682 $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
684 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
685 print_r(array("dump for {$this->table}", $ret));
694 * Convert a table name into a class name -> override this if you want a different mapping
697 * @return string class name;
701 function getClassNameFromTableName($table)
703 $options = &PEAR::getStaticProperty('DB_DataObject','options');
704 $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
705 return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
710 * Convert a table name into a file name -> override this if you want a different mapping
713 * @return string file name;
717 function getFileNameFromTableName($table)
719 $options = &PEAR::getStaticProperty('DB_DataObject','options');
720 $base = $options['class_location'];
721 if (strpos($base,'%s') !== false) {
722 $base = dirname($base);
724 if (!file_exists($base)) {
725 require_once 'System.php';
726 System::mkdir(array('-p',$base));
728 if (strpos($options['class_location'],'%s') !== false) {
729 $outfilename = sprintf($options['class_location'],
730 preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
732 $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
740 * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
743 * @return string method name;
747 function getMethodNameFromColumnName($col)
749 return ucfirst($col);
756 * building the class files
757 * for each of the tables output a file!
759 function generateClasses()
761 //echo "Generating Class files: \n";
762 $options = &PEAR::getStaticProperty('DB_DataObject','options');
765 if ($extends = @$options['extends']) {
766 $this->_extends = $extends;
767 $this->_extendsFile = $options['extends_location'];
770 foreach($this->tables as $this->table) {
771 $this->table = trim($this->table);
772 $this->classname = $this->getClassNameFromTableName($this->table);
774 $outfilename = $this->getFileNameFromTableName($this->table);
777 if (file_exists($outfilename)) {
778 // file_get_contents???
779 $oldcontents = implode('',file($outfilename));
782 $out = $this->_generateClassTable($oldcontents);
783 $this->debug( "writing $this->classname\n");
784 $tmpname = tempnam(session_save_path(),'DataObject_');
786 $fh = fopen($tmpname, "w");
789 $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
791 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
792 if (!@rename($tmpname, $outfilename)) {
793 unlink($outfilename);
794 rename($tmpname, $outfilename);
797 chmod($outfilename, $perms);
803 * class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx
808 var $_extends = 'DB_DataObject';
811 * line to use for require('DB/DataObject.php');
816 var $_extendsFile = "DB/DataObject.php";
819 * class being generated
827 * The table class geneation part - single file.
832 function _generateClassTable($input = '')
834 // title = expand me!
836 $head = "<?php\n/**\n * Table Definition for {$this->table}\n";
837 $head .= $this->derivedHookPageLevelDocBlock();
839 $head .= $this->derivedHookExtendsDocBlock();
843 $head .= "require_once '{$this->_extendsFile}';\n\n";
844 // add dummy class header in...
846 $head .= $this->derivedHookClassDocBlock();
847 $head .= "class {$this->classname} extends {$this->_extends} \n{";
849 $body = "\n ###START_AUTOCODE\n";
850 $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
852 $padding = (30 - strlen($this->table));
853 $padding = ($padding < 2) ? 2 : $padding;
855 $p = str_repeat(' ',$padding) ;
857 $options = &PEAR::getStaticProperty('DB_DataObject','options');
860 $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
861 $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
864 $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
867 // if we are using the option database_{databasename} = dsn
868 // then we should add var $_database = here
869 // as database names may not always match..
874 if (isset($options["database_{$this->_database}"])) {
875 $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
879 if (!empty($options['generator_novars'])) {
883 $defs = $this->_definitions[$this->table];
885 // show nice information!
886 $connections = array();
888 foreach($defs as $t) {
889 if (!strlen(trim($t->name))) {
892 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
893 echo "*****************************************************************\n".
894 "** WARNING COLUMN NAME UNUSABLE **\n".
895 "** Found column '{$t->name}', of type '{$t->type}' **\n".
896 "** Since this column name can't be converted to a php variable **\n".
897 "** name, and the whole idea of mapping would result in a mess **\n".
898 "** This column has been ignored... **\n".
899 "*****************************************************************\n";
904 $padding = (30 - strlen($t->name));
905 if ($padding < 2) $padding =2;
906 $p = str_repeat(' ',$padding) ;
908 $body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->flags}\n";
910 // can not do set as PEAR::DB table info doesnt support it.
911 //if (substr($t->Type,0,3) == "set")
912 // $sets[$t->Field] = "array".substr($t->Type,3);
913 $body .= $this->derivedHookVar($t,$padding);
916 // THIS IS TOTALLY BORKED old FC creation
917 // IT WILL BE REMOVED!!!!! in DataObjects 1.6
918 // grep -r __clone * to find all it's uses
919 // and replace them with $x = clone($y);
920 // due to the change in the PHP5 clone design.
922 if ( substr(phpversion(),0,1) < 5) {
924 $body .= " /* ZE2 compatibility trick*/\n";
925 $body .= " function __clone() { return \$this;}\n";
928 // simple creation tools ! (static stuff!)
930 $body .= " /* Static get */\n";
931 $body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
933 // generate getter and setter methods
934 $body .= $this->_generateGetters($input);
935 $body .= $this->_generateSetters($input);
938 theoretically there is scope here to introduce 'list' methods
939 based up 'xxxx_up' column!!! for heiracitcal trees..
943 //foreach ($sets as $k=>$v) {
944 // $kk = strtoupper($k);
945 // $body .=" function getSets{$k}() { return {$v}; }\n";
948 if (!empty($options['generator_no_ini'])) {
949 $def = $this->_generateDefinitionsTable(); // simplify this!?
950 $body .= $this->_generateTableFunction($def['table']);
951 $body .= $this->_generateKeysFunction($def['keys']);
952 $body .= $this->_generateSequenceKeyFunction($def);
953 $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
954 } else if (!empty($options['generator_add_defaults'])) {
955 // I dont really like doing it this way (adding another option)
956 // but it helps on older projects.
957 $def = $this->_generateDefinitionsTable(); // simplify this!?
958 $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
961 $body .= $this->derivedHookFunctions($input);
963 $body .= "\n /* the code above is auto generated do not remove the tag below */";
964 $body .= "\n ###END_AUTOCODE\n";
969 if (!empty($options['generator_add_validate_stubs'])) {
970 foreach($defs as $t) {
971 if (!strlen(trim($t->name))) {
974 $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
976 if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
979 $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
987 $full = $head . $body . $foot;
992 if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
995 if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
1000 /* this will only replace extends DB_DataObject by default,
1001 unless use set generator_class_rewrite to ANY or a name*/
1003 $class_rewrite = 'DB_DataObject';
1004 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1005 if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
1006 $class_rewrite = 'DB_DataObject';
1008 if ($class_rewrite == 'ANY') {
1009 $class_rewrite = '[a-z_]+';
1012 $input = preg_replace(
1013 '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
1014 "\nclass {$this->classname} extends {$this->_extends} \n{\n",
1017 $ret = preg_replace(
1018 '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
1021 if (!strlen($ret)) {
1022 return PEAR::raiseError(
1023 "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
1024 "pcre.backtrack_limit=1000000\n".
1025 "pcre.recursion_limit=1000000\n"
1026 ,null, PEAR_ERROR_DIE);
1033 * hook to add extra methods to all classes
1035 * called once for each class, use with $this->table and
1036 * $this->_definitions[$this->table], to get data out of the current table,
1037 * use it to add extra methods to the default classes.
1040 * @return string added to class eg. functions.
1042 function derivedHookFunctions($input = "")
1044 // This is so derived generator classes can generate functions
1045 // It MUST NOT be changed here!!!
1050 * hook for var lines
1051 * called each time a var line is generated, override to add extra var
1054 * @param object t containing type,len,flags etc. from tableInfo call
1055 * @param int padding number of spaces
1057 * @return string added to class eg. functions.
1059 function derivedHookVar(&$t,$padding)
1061 // This is so derived generator classes can generate variabels
1062 // It MUST NOT be changed here!!!
1067 * hook to add extra page-level (in terms of phpDocumentor) DocBlock
1069 * called once for each class, use it add extra page-level docs
1071 * @return string added to class eg. functions.
1073 function derivedHookPageLevelDocBlock() {
1078 * hook to add extra doc block (in terms of phpDocumentor) to extend string
1080 * called once for each class, use it add extra comments to extends
1081 * string (require_once...)
1083 * @return string added to class eg. functions.
1085 function derivedHookExtendsDocBlock() {
1090 * hook to add extra class level DocBlock (in terms of phpDocumentor)
1092 * called once for each class, use it add extra comments to class
1093 * string (require_once...)
1095 * @return string added to class eg. functions.
1097 function derivedHookClassDocBlock() {
1104 * getProxyFull - create a class definition on the fly and instantate it..
1106 * similar to generated files - but also evals the class definitoin code.
1109 * @param string database name
1110 * @param string table name of table to create proxy for.
1113 * @return object Instance of class. or PEAR Error
1116 function getProxyFull($database,$table)
1119 if ($err = $this->fillTableSchema($database,$table)) {
1124 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1125 $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
1127 if ($extends = @$options['extends']) {
1128 $this->_extends = $extends;
1129 $this->_extendsFile = $options['extends_location'];
1131 $classname = $this->classname = $this->getClassNameFromTableName($this->table);
1133 $out = $this->_generateClassTable();
1136 return new $classname;
1141 * fillTableSchema - set the database schema on the fly
1145 * @param string database name
1146 * @param string table name of table to create schema info for
1148 * @return none | PEAR::error()
1151 function fillTableSchema($database,$table)
1153 global $_DB_DATAOBJECT;
1154 // a little bit of sanity testing.
1155 if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {
1156 return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1159 $this->_database = $database;
1162 $table = trim($table);
1164 // a little bit of sanity testing.
1165 if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {
1166 return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1168 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1171 $options = PEAR::getStaticProperty('DB_DataObject','options');
1172 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1173 $is_MDB2 = ($db_driver != 'DB') ? true : false;
1176 // try getting a list of schema tables first. (postgres)
1177 $__DB->expectError(DB_ERROR_UNSUPPORTED);
1178 $this->tables = $__DB->getListOf('schema.tables');
1182 * set portability and some modules to fetch the informations
1184 $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
1185 $__DB->loadModule('Manager');
1186 $__DB->loadModule('Reverse');
1188 $quotedTable = !empty($options['quote_identifiers']) ?
1189 $__DB->quoteIdentifier($table) : $table;
1192 $defs = $__DB->tableInfo($quotedTable);
1194 $defs = $__DB->reverse->tableInfo($quotedTable);
1195 foreach ($defs as $k => $v) {
1196 if (!isset($defs[$k]['length'])) {
1199 $defs[$k]['len'] = $defs[$k]['length'];
1206 if (PEAR::isError($defs)) {
1209 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
1210 $this->debug("getting def for $database/$table",'fillTable');
1211 $this->debug(print_r($defs,true),'defs');
1213 // cast all definitions to objects - as we deal with that better.
1216 foreach($defs as $def) {
1217 if (is_array($def)) {
1218 $this->_definitions[$table][] = (object) $def;
1222 $this->table = trim($table);
1223 $ret = $this->_generateDefinitionsTable();
1225 $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
1226 $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
1232 * Generate getter methods for class definition
1234 * @param string $input Existing class contents
1238 function _generateGetters($input)
1241 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1244 // only generate if option is set to true
1245 if (empty($options['generate_getters'])) {
1249 // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1250 $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1253 $defs = $this->_definitions[$this->table];
1255 // loop through properties and create getter methods
1256 foreach ($defs = $defs as $t) {
1258 // build mehtod name
1259 $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
1261 if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1265 $getters .= " /**\n";
1266 $getters .= " * Getter for \${$t->name}\n";
1268 $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
1269 : " * @return {$t->type}\n";
1270 $getters .= " * @access public\n";
1271 $getters .= " */\n";
1272 $getters .= (substr(phpversion(),0,1) > 4) ? ' public '
1274 $getters .= "function $methodName() {\n";
1275 $getters .= " return \$this->{$t->name};\n";
1276 $getters .= " }\n\n";
1285 * Generate setter methods for class definition
1287 * @param string Existing class contents
1291 function _generateSetters($input)
1294 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1297 // only generate if option is set to true
1298 if (empty($options['generate_setters'])) {
1302 // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1303 $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1306 $defs = $this->_definitions[$this->table];
1308 // loop through properties and create setter methods
1309 foreach ($defs = $defs as $t) {
1311 // build mehtod name
1312 $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
1314 if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1318 $setters .= " /**\n";
1319 $setters .= " * Setter for \${$t->name}\n";
1321 $setters .= " * @param mixed input value\n";
1322 $setters .= " * @access public\n";
1323 $setters .= " */\n";
1324 $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
1326 $setters .= "function $methodName(\$value) {\n";
1327 $setters .= " \$this->{$t->name} = \$value;\n";
1328 $setters .= " }\n\n";
1335 * Generate table Function - used when generator_no_ini is set.
1337 * @param array table array.
1341 function _generateTableFunction($def)
1343 $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
1346 " function table()\n" .
1350 foreach($def as $k=>$v) {
1352 foreach($defines as $dn) {
1353 if ($v & constant('DB_DATAOBJECT_' . $dn)) {
1354 $str .= ' + DB_DATAOBJECT_' . $dn;
1357 if (strlen($str) > 1) {
1358 $str = substr($str,3); // strip the 0 +
1360 // hopefully addslashes is good enough here!!!
1361 $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
1363 return $ret . " );\n" .
1370 * Generate keys Function - used generator_no_ini is set.
1372 * @param array keys array.
1376 function _generateKeysFunction($def)
1380 " function keys()\n" .
1384 foreach($def as $k=>$type) {
1385 // hopefully addslashes is good enough here!!!
1386 $ret .= '\''.addslashes($k).'\', ';
1388 $ret = preg_replace('#, $#', '', $ret);
1389 return $ret . ");\n" .
1396 * Generate sequenceKey Function - used generator_no_ini is set.
1398 * @param array table and key definition.
1402 function _generateSequenceKeyFunction($def)
1406 // DB_DataObject::debugLevel(5);
1407 global $_DB_DATAOBJECT;
1411 $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
1412 $realkeys = $def['keys'];
1413 $keys = array_keys($realkeys);
1414 $usekey = isset($keys[0]) ? $keys[0] : false;
1415 $table = $def['table'];
1423 $ar = array(false,false,false);
1424 if ($usekey !== false) {
1425 if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
1426 $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
1427 if (strpos($usekey,':') !== false) {
1428 list($usekey,$seqname) = explode(':',$usekey);
1432 if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
1433 ($table[$usekey] & DB_DATAOBJECT_INT) &&
1434 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
1436 // use native sequence keys.
1437 $ar = array($usekey,true,$seqname);
1439 // use generated sequence keys..
1440 if ($table[$usekey] & DB_DATAOBJECT_INT) {
1441 $ar = array($usekey,false,$seqname);
1450 " function sequenceKey() // keyname, use native, native name\n" .
1453 foreach($ar as $v) {
1454 switch (gettype($v)) {
1456 $ret .= ($v ? 'true' : 'false') . ', ';
1460 $ret .= "'" . $v . "', ";
1468 $ret = preg_replace('#, $#', '', $ret);
1469 return $ret . ");\n" .
1474 * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
1475 * Only supports mysql and mysqli ... welcome ideas for more..
1478 * @param array table and key definition.
1482 function _generateDefaultsFunction($table,$defs)
1484 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1485 if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
1486 return; // cant handle non-mysql introspection for defaults.
1488 $options = PEAR::getStaticProperty('DB_DataObject','options');
1489 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1490 $method = $db_driver == 'DB' ? 'getAll' : 'queryAll';
1491 $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
1492 $defaults = array();
1493 foreach($res as $ar) {
1494 // this is initially very dumb... -> and it may mess up..
1495 $type = $defs[$ar['Field']];
1499 case (is_null( $ar['Default'])):
1500 $defaults[$ar['Field']] = 'null';
1503 case ($type & DB_DATAOBJECT_DATE):
1504 case ($type & DB_DATAOBJECT_TIME):
1505 case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
1508 case ($type & DB_DATAOBJECT_BOOL):
1509 $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
1513 case ($type & DB_DATAOBJECT_STR):
1514 $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
1518 default: // hopefully eveything else... - numbers etc.
1519 if (!strlen($ar['Default'])) {
1522 if (is_numeric($ar['Default'])) {
1523 $defaults[$ar['Field']] = $ar['Default'];
1528 //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
1530 if (empty($defaults)) {
1535 " function defaults() // column default values \n" .
1538 foreach($defaults as $k=>$v) {
1539 $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
1541 return $ret . " );\n" .