3 * Generation tools for DB_DataObject
7 * LICENSE: This source file is subject to version 3.01 of the PHP license
8 * that is available through the world-wide-web at the following URI:
9 * http://www.php.net/license/3_01.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_01.txt PHP License 3.01
18 * @version CVS: $Id: Generator.php 327926 2012-10-08 02:42:09Z alan_k $
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!
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
95 var $_fkeys; // active tablename
98 * The 'starter' = call this to start the process
105 $options = &PEAR::getStaticProperty('DB_DataObject','options');
106 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
108 $databases = array();
109 foreach($options as $k=>$v) {
110 if (substr($k,0,9) == 'database_') {
111 $databases[substr($k,9)] = $v;
115 if (isset($options['database'])) {
116 if ($db_driver == 'DB') {
117 require_once 'DB.php';
118 $dsn = DB::parseDSN($options['database']);
120 require_once 'MDB2.php';
121 $dsn = MDB2::parseDSN($options['database']);
124 if (!isset($database[$dsn['database']])) {
125 $databases[$dsn['database']] = $options['database'];
129 foreach($databases as $databasename => $database) {
133 $this->debug("CREATING FOR $databasename\n");
134 $class = get_class($this);
136 $t->_database_dsn = $database;
139 $t->_database = $databasename;
140 if ($db_driver == 'DB') {
141 require_once 'DB.php';
142 $dsn = DB::parseDSN($database);
144 require_once 'MDB2.php';
145 $dsn = MDB2::parseDSN($database);
148 if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
149 $t->_database = basename($t->_database);
151 $t->_createTableList();
152 $t->_createForiegnKeys();
154 foreach(get_class_methods($class) as $method) {
155 if (substr($method,0,8 ) != 'generate') {
158 $this->debug("calling $method");
162 $this->debug("DONE\n\n");
166 * Output File was config object, now just string
167 * Used to generate the Tables
169 * @var string outputbuffer for table definitions
175 * Build a list of tables;
176 * and store it in $this->tables and $this->_definitions[tablename];
181 function _createTableList()
185 $options = &PEAR::getStaticProperty('DB_DataObject','options');
189 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
191 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
192 $is_MDB2 = ($db_driver != 'DB') ? true : false;
194 if (is_object($__DB) && is_a($__DB , 'PEAR_Error')) {
195 return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
199 // try getting a list of schema tables first. (postgres)
200 $__DB->expectError(DB_ERROR_UNSUPPORTED);
201 $this->tables = $__DB->getListOf('schema.tables');
205 * set portability and some modules to fetch the informations
207 $db_options = PEAR::getStaticProperty('MDB2','options');
208 if (empty($db_options)) {
209 $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
212 $__DB->loadModule('Manager');
213 $__DB->loadModule('Reverse');
216 if ((empty($this->tables) || (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')))) {
217 //if that fails fall back to clasic tables list.
219 // try getting a list of schema tables first. (postgres)
220 $__DB->expectError(DB_ERROR_UNSUPPORTED);
221 $this->tables = $__DB->getListOf('tables');
224 $this->tables = $__DB->manager->listTables();
225 $sequences = $__DB->manager->listSequences();
226 foreach ($sequences as $k => $v) {
227 $this->tables[] = $__DB->getSequenceName($v);
232 if (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')) {
233 return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
236 // build views as well if asked to.
237 if (!empty($options['build_views'])) {
239 $views = $__DB->getListOf(is_string($options['build_views']) ?
240 $options['build_views'] : 'views');
242 $views = $__DB->manager->listViews();
244 if (is_object($views) && is_a($views,'PEAR_Error')) {
245 return PEAR::raiseError(
246 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
252 $this->tables = array_merge ($this->tables, $views);
255 // declare a temporary table to be filled with matching tables names
256 $tmp_table = array();
259 foreach($this->tables as $table) {
260 if (isset($options['generator_include_regex']) &&
261 !preg_match($options['generator_include_regex'],$table)) {
262 $this->debug("SKIPPING (generator_include_regex) : $table");
266 if (isset($options['generator_exclude_regex']) &&
267 preg_match($options['generator_exclude_regex'],$table)) {
271 $strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema'];
272 $strip = is_numeric($strip) ? (bool) $strip : $strip;
273 $strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip;
275 // postgres strip the schema bit from the
276 if (!empty($strip) ) {
278 if (!is_string($strip) || preg_match($strip, $table)) {
279 $bits = explode('.', $table,2);
281 if (count($bits) > 1) {
286 $this->debug("EXTRACTING : $table");
288 $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
289 $__DB->quoteIdentifier($table) : $table;
293 $defs = $__DB->tableInfo($quotedTable);
295 $defs = $__DB->reverse->tableInfo($quotedTable);
296 // rename the length value, so it matches db's return.
300 if (is_object($defs) && is_a($defs,'PEAR_Error')) {
301 // running in debug mode should pick this up as a big warning..
302 $this->debug("Error reading tableInfo: $table");
303 $this->raiseError('Error reading tableInfo, '. $defs->toString());
306 // cast all definitions to objects - as we deal with that better.
310 foreach($defs as $def) {
311 if (!is_array($def)) {
314 // rename the length value, so it matches db's return.
315 if (isset($def['length']) && !isset($def['len'])) {
316 $def['len'] = $def['length'];
318 $this->_definitions[$table][] = (object) $def;
321 // we find a matching table, just store it into a temporary array
322 $tmp_table[] = $table;
326 // the temporary table array is now the right one (tables names matching
327 // with regex expressions have been removed)
328 $this->tables = $tmp_table;
330 //print_r($this->_definitions);
334 * Auto generation of table data.
336 * it will output to db_oo_{database} the table definitions
341 function generateDefinitions()
343 $this->debug("Generating Definitions file: ");
344 if (!$this->tables) {
345 $this->debug("-- NO TABLES -- \n");
349 $options = &PEAR::getStaticProperty('DB_DataObject','options');
352 //$this->_newConfig = new Config('IniFile');
353 $this->_newConfig = '';
354 foreach($this->tables as $this->table) {
355 $this->_generateDefinitionsTable();
358 // dont generate a schema if location is not set
359 // it's created on the fly!
360 if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
363 if (!empty($options['generator_no_ini'])) { // built in ini files..
366 $base = @$options['schema_location'];
367 if (isset($options["ini_{$this->_database}"])) {
368 $file = $options["ini_{$this->_database}"];
370 $file = "{$base}/{$this->_database}.ini";
373 if (!file_exists(dirname($file))) {
374 require_once 'System.php';
375 System::mkdir(array('-p','-m',0755,dirname($file)));
377 $this->debug("Writing ini as {$file}\n");
379 $tmpname = tempnam(session_save_path(),'DataObject_');
380 //print_r($this->_newConfig);
381 $fh = fopen($tmpname,'w');
383 return PEAR::raiseError(
384 "Failed to create temporary file: $tmpname\n".
385 "make sure session.save_path is set and is writable\n"
386 ,null, PEAR_ERROR_DIE);
388 fwrite($fh,$this->_newConfig);
390 $perms = file_exists($file) ? fileperms($file) : 0755;
391 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
393 if (!@rename($tmpname, $file)) {
395 rename($tmpname, $file);
398 //$ret = $this->_newConfig->writeInput($file,false);
400 //if (PEAR::isError($ret) ) {
401 // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
405 * create the data for Foreign Keys (for links.ini)
406 * Currenly only works with mysql / mysqli / posgtreas
407 * to use, you must set option: generate_links=true
409 * @author Pascal Schöni
412 function _createForiegnKeys()
414 $options = PEAR::getStaticProperty('DB_DataObject','options');
415 if (empty($options['generate_links'])) {
418 $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
419 if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
420 echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
421 return; // cant handle non-mysql introspection for defaults.
423 $this->debug("generateForeignKeys: Start");
424 $DB = $this->getDatabaseConnection();
429 switch ($DB->phptype) {
433 foreach($this->tables as $this->table) {
434 $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
435 $res =& $DB->query("SELECT
436 pg_catalog.pg_get_constraintdef(r.oid, true) AS condef
437 FROM pg_catalog.pg_constraint r,
438 pg_catalog.pg_class c
439 WHERE c.oid=r.conrelid
441 AND c.relname = '" . $quotedTable . "'");
442 if (PEAR::isError($res)) {
443 die($res->getMessage());
446 while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
448 // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049
450 "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i",
453 if (!count($treffer)) {
456 $fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3];
466 foreach($this->tables as $this->table) {
467 $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
469 $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable );
471 if (PEAR::isError($res)) {
472 die($res->getMessage());
475 $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
477 // Extract FOREIGN KEYS
479 "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
484 if (!count($treffer)) {
487 foreach($treffer as $i=> $tref) {
488 $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
506 * generate Foreign Keys (for links.ini)
507 * Currenly only works with mysql / mysqli
508 * to use, you must set option: generate_links=true
510 * @author Pascal Schöni
512 function generateForeignKeys()
514 $options = PEAR::getStaticProperty('DB_DataObject','options');
515 if (empty($options['generate_links'])) {
518 $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
519 if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
520 echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
521 return; // cant handle non-mysql introspection for defaults.
523 $this->debug("generateForeignKeys: Start");
528 foreach($fk as $table => $details) {
529 $links_ini .= "[$table]\n";
530 foreach ($details as $col => $ref) {
531 $links_ini .= "$col = $ref\n";
536 // dont generate a schema if location is not set
537 // it's created on the fly!
538 $options = PEAR::getStaticProperty('DB_DataObject','options');
540 if (!empty($options['schema_location'])) {
541 $file = "{$options['schema_location']}/{$this->_database}.links.ini";
542 } elseif (isset($options["ini_{$this->_database}"])) {
543 $file = preg_replace('/\.ini/','.links.ini',$options["ini_{$this->_database}"]);
545 $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set");
550 if (!file_exists(dirname($file))) {
551 mkdir(dirname($file),0755, true);
554 $this->debug("Writing ini as {$file}\n");
556 //touch($file); // not sure why this is needed?
557 $tmpname = tempnam(session_save_path(),'DataObject_');
559 $fh = fopen($tmpname,'w');
561 return PEAR::raiseError(
562 "Failed to create temporary file: $tmpname\n".
563 "make sure session.save_path is set and is writable\n"
564 ,null, PEAR_ERROR_DIE);
566 fwrite($fh,$links_ini);
568 $perms = file_exists($file) ? fileperms($file) : 0755;
569 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
570 if (!@rename($tmpname, $file)) {
572 rename($tmpname, $file);
574 chmod($file, $perms);
579 * The table geneation part
582 * @return tabledef and keys array.
584 function _generateDefinitionsTable()
586 global $_DB_DATAOBJECT;
587 $options = PEAR::getStaticProperty('DB_DataObject','options');
588 $defs = $this->_definitions[$this->table];
589 $this->_newConfig .= "\n[{$this->table}]\n";
590 $keys_out = "\n[{$this->table}__keys]\n";
591 $keys_out_primary = '';
592 $keys_out_secondary = '';
593 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
594 echo "TABLE STRUCTURE FOR {$this->table}\n";
597 $DB = $this->getDatabaseConnection();
598 $dbtype = $DB->phptype;
605 $ret_keys_primary = array();
606 $ret_keys_secondary = array();
610 foreach($defs as $t) {
616 switch (strtoupper($t->type)) {
619 case 'INT2': // postgres
620 case 'INT4': // postgres
621 case 'INT8': // postgres
622 case 'SERIAL4': // postgres
623 case 'SERIAL8': // postgres
629 $type = DB_DATAOBJECT_INT;
631 $type += DB_DATAOBJECT_BOOL;
637 case 'DOUBLE PRECISION': // double precision (firebird)
639 case 'FLOAT4': // real (postgres)
640 case 'FLOAT8': // double precision (postgres)
642 case 'MONEY': // mssql and maybe others
644 case 'NUMBER': // oci8
645 $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
649 $type = DB_DATAOBJECT_INT;
656 $type = DB_DATAOBJECT_BOOL;
657 // postgres needs to quote '0'
658 if ($dbtype == 'pgsql') {
659 $type += DB_DATAOBJECT_STR;
670 case 'SET': // not really but oh well
672 case 'POINT': // mysql geometry stuff - not really string - but will do..
674 case 'TIMESTAMPTZ': // postgres
675 case 'BPCHAR': // postgres
676 case 'INTERVAL': // postgres (eg. '12 days')
678 case 'CIDR': // postgres IP net spec
679 case 'INET': // postgres IP
680 case 'MACADDR': // postgress network Mac address.
682 case 'INTEGER[]': // postgres type
683 case 'BOOLEAN[]': // postgres type
685 $type = DB_DATAOBJECT_STR;
691 case '_TEXT': //postgres (?? view ??)
693 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
698 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
702 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
708 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
711 case 'TIMESTAMP': // do other databases use this???
713 $type = ($dbtype == 'mysql') ?
714 DB_DATAOBJECT_MYSQLTIMESTAMP :
715 DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
719 case 'BLOB': /// these should really be ignored!!!???
724 case 'CLOB': // oracle character lob support
726 case 'BYTEA': // postgres blob support..
727 $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
731 echo "*****************************************************************\n".
732 "** WARNING UNKNOWN TYPE **\n".
733 "** Found column '{$t->name}', of type '{$t->type}' **\n".
734 "** Please submit a bug, describe what type you expect this **\n".
735 "** column to be **\n".
736 "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
737 "** Try using MDB2 as the backend - eg set the config option **\n".
738 "** db_driver = MDB2 **\n".
739 "*****************************************************************\n";
744 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
745 echo "*****************************************************************\n".
746 "** WARNING COLUMN NAME UNUSABLE **\n".
747 "** Found column '{$t->name}', of type '{$t->type}' **\n".
748 "** Since this column name can't be converted to a php variable **\n".
749 "** name, and the whole idea of mapping would result in a mess **\n".
750 "** This column has been ignored... **\n".
751 "*****************************************************************\n";
755 if (!strlen(trim($t->name))) {
756 continue; // is this a bug?
759 if (preg_match('/not[ _]null/i',$t->flags)) {
760 $type += DB_DATAOBJECT_NOTNULL;
764 if (in_array($t->name,array('null','yes','no','true','false'))) {
765 echo "*****************************************************************\n".
767 "** Found column '{$t->name}', which is invalid in an .ini file **\n".
768 "** This line will not be writen to the file - you will have **\n".
769 "** define the keys()/method manually. **\n".
770 "*****************************************************************\n";
773 $this->_newConfig .= "{$t->name} = $type\n";
776 $ret['table'][$t->name] = $type;
777 // i've no idea if this will work well on other databases?
778 // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
779 // if no keys exist fall back to using unique
780 //echo "\n{$t->name} => {$t->flags}\n";
781 $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique';
784 if (preg_match('/(auto_increment|nextval\(([^)]*))/i',rawurldecode($t->flags),$m)
785 || (isset($t->autoincrement) && ($t->autoincrement === true))) {
788 if ($DB->phptype == 'pgsql' && !empty($m[2])) {
789 $sn = preg_replace('/[("]+/','', $m[2]);
790 //echo urldecode($t->flags) . "\n" ;
792 // native sequences = 2
794 $keys_out_primary .= "{$t->name} = $sn\n";
796 $ret_keys_primary[$t->name] = $sn;
798 } else if ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i',$t->flags)) {
801 if (!preg_match("/(primary)/i",$t->flags)) {
806 $keys_out_secondary .= "{$t->name} = {$key_type}\n";
808 $ret_keys_secondary[$t->name] = $key_type;
814 $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
815 $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
817 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
818 print_r(array("dump for {$this->table}", $ret));
827 * Convert a table name into a class name -> override this if you want a different mapping
830 * @return string class name;
834 function getClassNameFromTableName($table)
836 $options = &PEAR::getStaticProperty('DB_DataObject','options');
837 $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
838 return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
843 * Convert a table name into a file name -> override this if you want a different mapping
846 * @return string file name;
850 function getFileNameFromTableName($table)
852 $options = &PEAR::getStaticProperty('DB_DataObject','options');
853 $base = $options['class_location'];
854 if (strpos($base,'%s') !== false) {
855 $base = dirname($base);
857 if (!file_exists($base)) {
858 require_once 'System.php';
859 System::mkdir(array('-p',$base));
861 if (strpos($options['class_location'],'%s') !== false) {
862 $outfilename = sprintf($options['class_location'],
863 preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
865 $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
873 * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
876 * @return string method name;
880 function getMethodNameFromColumnName($col)
882 return ucfirst($col);
889 * building the class files
890 * for each of the tables output a file!
892 function generateClasses()
894 //echo "Generating Class files: \n";
895 $options = &PEAR::getStaticProperty('DB_DataObject','options');
897 $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
898 $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
901 foreach($this->tables as $this->table) {
902 $this->table = trim($this->table);
903 $this->classname = $this->getClassNameFromTableName($this->table);
905 $outfilename = $this->getFileNameFromTableName($this->table);
908 if (file_exists($outfilename)) {
909 // file_get_contents???
910 $oldcontents = implode('',file($outfilename));
913 $out = $this->_generateClassTable($oldcontents);
914 $this->debug( "writing $this->classname\n");
915 $tmpname = tempnam(session_save_path(),'DataObject_');
917 $fh = fopen($tmpname, "w");
919 return PEAR::raiseError(
920 "Failed to create temporary file: $tmpname\n".
921 "make sure session.save_path is set and is writable\n"
922 ,null, PEAR_ERROR_DIE);
926 $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
928 // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
929 if (!@rename($tmpname, $outfilename)) {
930 unlink($outfilename);
931 rename($tmpname, $outfilename);
934 chmod($outfilename, $perms);
940 * class being extended (can be overridden by [DB_DataObject] extends=xxxx
945 var $_extends = 'DB_DataObject';
948 * line to use for require('DB/DataObject.php');
953 var $_extendsFile = "DB/DataObject.php";
956 * class being generated
964 * The table class geneation part - single file.
969 function _generateClassTable($input = '')
971 // title = expand me!
973 $head = "<?php\n/**\n * Table Definition for {$this->table}\n";
974 $head .= $this->derivedHookPageLevelDocBlock();
976 $head .= $this->derivedHookExtendsDocBlock();
980 $head .= "require_once '{$this->_extendsFile}';\n\n";
981 // add dummy class header in...
983 $head .= $this->derivedHookClassDocBlock();
984 $head .= "class {$this->classname} extends {$this->_extends} \n{";
986 $body = "\n ###START_AUTOCODE\n";
987 $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
990 $p = str_repeat(' ',max(2, (18 - strlen($this->table)))) ;
992 $options = &PEAR::getStaticProperty('DB_DataObject','options');
995 $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
996 $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
999 $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
1001 // if we are using the option database_{databasename} = dsn
1002 // then we should add var $_database = here
1003 // as database names may not always match..
1005 if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) {
1006 DB_DataObject::_loadConfig();
1009 // Only include the $_database property if the omit_database_var is unset or false
1011 if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
1012 $p = str_repeat(' ', max(2, (16 - strlen($this->_database))));
1013 $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
1017 if (!empty($options['generator_novars'])) {
1021 $defs = $this->_definitions[$this->table];
1023 // show nice information!
1024 $connections = array();
1027 foreach($defs as $t) {
1028 if (!strlen(trim($t->name))) {
1031 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
1032 echo "*****************************************************************\n".
1033 "** WARNING COLUMN NAME UNUSABLE **\n".
1034 "** Found column '{$t->name}', of type '{$t->type}' **\n".
1035 "** Since this column name can't be converted to a php variable **\n".
1036 "** name, and the whole idea of mapping would result in a mess **\n".
1037 "** This column has been ignored... **\n".
1038 "*****************************************************************\n";
1042 $p = str_repeat(' ',max(2, (30 - strlen($t->name))));
1044 $length = empty($t->len) ? '' : '('.$t->len.')';
1045 $body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->flags}\n";
1047 // can not do set as PEAR::DB table info doesnt support it.
1048 //if (substr($t->Type,0,3) == "set")
1049 // $sets[$t->Field] = "array".substr($t->Type,3);
1050 $body .= $this->derivedHookVar($t,strlen($p));
1053 $body .= $this->derivedHookPostVar($defs);
1055 // THIS IS TOTALLY BORKED old FC creation
1056 // IT WILL BE REMOVED!!!!! in DataObjects 1.6
1057 // grep -r __clone * to find all it's uses
1058 // and replace them with $x = clone($y);
1059 // due to the change in the PHP5 clone design.
1061 if ( substr(phpversion(),0,1) < 5) {
1063 $body .= " /* ZE2 compatibility trick*/\n";
1064 $body .= " function __clone() { return \$this;}\n";
1068 // depricated - in here for BC...
1069 if (!empty($options['static_get'])) {
1071 // simple creation tools ! (static stuff!)
1073 $body .= " /* Static get */\n";
1074 $body .= " $static function staticGet(\$k,\$v=NULL) { " .
1075 "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n";
1077 // generate getter and setter methods
1078 $body .= $this->_generateGetters($input);
1079 $body .= $this->_generateSetters($input);
1080 $body .= $this->_generateLinkMethods($input);
1082 theoretically there is scope here to introduce 'list' methods
1083 based up 'xxxx_up' column!!! for heiracitcal trees..
1087 //foreach ($sets as $k=>$v) {
1088 // $kk = strtoupper($k);
1089 // $body .=" function getSets{$k}() { return {$v}; }\n";
1092 if (!empty($options['generator_no_ini'])) {
1093 $def = $this->_generateDefinitionsTable(); // simplify this!?
1094 $body .= $this->_generateTableFunction($def['table']);
1095 $body .= $this->_generateKeysFunction($def['keys']);
1096 $body .= $this->_generateSequenceKeyFunction($def);
1097 $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
1098 } else if (!empty($options['generator_add_defaults'])) {
1099 // I dont really like doing it this way (adding another option)
1100 // but it helps on older projects.
1101 $def = $this->_generateDefinitionsTable(); // simplify this!?
1102 $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
1105 $body .= $this->derivedHookFunctions($input);
1107 $body .= "\n /* the code above is auto generated do not remove the tag below */";
1108 $body .= "\n ###END_AUTOCODE\n";
1113 if (!empty($options['generator_add_validate_stubs'])) {
1114 foreach($defs as $t) {
1115 if (!strlen(trim($t->name))) {
1118 $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
1120 if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
1123 $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
1131 $full = $head . $body . $foot;
1136 if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
1139 if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
1144 /* this will only replace extends DB_DataObject by default,
1145 unless use set generator_class_rewrite to ANY or a name*/
1147 $class_rewrite = 'DB_DataObject';
1148 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1149 if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
1150 $class_rewrite = 'DB_DataObject';
1152 if ($class_rewrite == 'ANY') {
1153 $class_rewrite = '[a-z_]+';
1156 $input = preg_replace(
1157 '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
1158 "\nclass {$this->classname} extends {$this->_extends} \n{\n",
1161 $ret = preg_replace(
1162 '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
1165 if (!strlen($ret)) {
1166 return PEAR::raiseError(
1167 "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
1168 "pcre.backtrack_limit=1000000\n".
1169 "pcre.recursion_limit=1000000\n"
1170 ,null, PEAR_ERROR_DIE);
1177 * hook to add extra methods to all classes
1179 * called once for each class, use with $this->table and
1180 * $this->_definitions[$this->table], to get data out of the current table,
1181 * use it to add extra methods to the default classes.
1184 * @return string added to class eg. functions.
1186 function derivedHookFunctions($input = "")
1188 // This is so derived generator classes can generate functions
1189 // It MUST NOT be changed here!!!
1194 * hook for var lines
1195 * called each time a var line is generated, override to add extra var
1198 * @param object t containing type,len,flags etc. from tableInfo call
1199 * @param int padding number of spaces
1201 * @return string added to class eg. functions.
1203 function derivedHookVar(&$t,$padding)
1205 // This is so derived generator classes can generate variabels
1206 // It MUST NOT be changed here!!!
1210 * hook for after var lines (
1211 * called at the end of the output of var line have generated, override to add extra var
1214 * @param array cols containing array of objects with type,len,flags etc. from tableInfo call
1216 * @return string added to class eg. functions.
1218 function derivedHookPostVar($t)
1220 // This is so derived generator classes can generate variabels
1221 // It MUST NOT be changed here!!!
1225 * hook to add extra page-level (in terms of phpDocumentor) DocBlock
1227 * called once for each class, use it add extra page-level docs
1229 * @return string added to class eg. functions.
1231 function derivedHookPageLevelDocBlock() {
1236 * hook to add extra doc block (in terms of phpDocumentor) to extend string
1238 * called once for each class, use it add extra comments to extends
1239 * string (require_once...)
1241 * @return string added to class eg. functions.
1243 function derivedHookExtendsDocBlock() {
1248 * hook to add extra class level DocBlock (in terms of phpDocumentor)
1250 * called once for each class, use it add extra comments to class
1251 * string (require_once...)
1253 * @return string added to class eg. functions.
1255 function derivedHookClassDocBlock() {
1262 * getProxyFull - create a class definition on the fly and instantate it..
1264 * similar to generated files - but also evals the class definitoin code.
1267 * @param string database name
1268 * @param string table name of table to create proxy for.
1271 * @return object Instance of class. or PEAR Error
1274 function getProxyFull($database,$table)
1277 if ($err = $this->fillTableSchema($database,$table)) {
1282 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1283 $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
1285 $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
1286 $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
1288 $classname = $this->classname = $this->getClassNameFromTableName($this->table);
1290 $out = $this->_generateClassTable();
1293 return new $classname;
1298 * fillTableSchema - set the database schema on the fly
1302 * @param string database name
1303 * @param string table name of table to create schema info for
1305 * @return none | PEAR::error()
1308 function fillTableSchema($database,$table)
1310 global $_DB_DATAOBJECT;
1311 // a little bit of sanity testing.
1312 if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {
1313 return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1316 $this->_database = $database;
1319 $table = trim($table);
1321 // a little bit of sanity testing.
1322 if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {
1323 return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1325 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1328 $options = PEAR::getStaticProperty('DB_DataObject','options');
1329 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1330 $is_MDB2 = ($db_driver != 'DB') ? true : false;
1333 // try getting a list of schema tables first. (postgres)
1334 $__DB->expectError(DB_ERROR_UNSUPPORTED);
1335 $this->tables = $__DB->getListOf('schema.tables');
1339 * set portability and some modules to fetch the informations
1341 $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
1342 $__DB->loadModule('Manager');
1343 $__DB->loadModule('Reverse');
1345 $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
1346 $__DB->quoteIdentifier($table) : $table;
1349 $defs = $__DB->tableInfo($quotedTable);
1351 $defs = $__DB->reverse->tableInfo($quotedTable);
1352 foreach ($defs as $k => $v) {
1353 if (!isset($defs[$k]['length'])) {
1356 $defs[$k]['len'] = $defs[$k]['length'];
1363 if (PEAR::isError($defs)) {
1366 if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
1367 $this->debug("getting def for $database/$table",'fillTable');
1368 $this->debug(print_r($defs,true),'defs');
1370 // cast all definitions to objects - as we deal with that better.
1373 foreach($defs as $def) {
1374 if (is_array($def)) {
1375 $this->_definitions[$table][] = (object) $def;
1379 $this->table = trim($table);
1380 $ret = $this->_generateDefinitionsTable();
1382 $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
1383 $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
1389 * Generate getter methods for class definition
1391 * @param string $input Existing class contents
1395 function _generateGetters($input)
1398 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1401 // only generate if option is set to true
1402 if (empty($options['generate_getters'])) {
1406 // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1407 $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1410 $defs = $this->_definitions[$this->table];
1412 // loop through properties and create getter methods
1413 foreach ($defs = $defs as $t) {
1415 // build mehtod name
1416 $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
1418 if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1422 $getters .= " /**\n";
1423 $getters .= " * Getter for \${$t->name}\n";
1425 $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
1426 : " * @return {$t->type}\n";
1427 $getters .= " * @access public\n";
1428 $getters .= " */\n";
1429 $getters .= (substr(phpversion(),0,1) > 4) ? ' public '
1431 $getters .= "function $methodName() {\n";
1432 $getters .= " return \$this->{$t->name};\n";
1433 $getters .= " }\n\n";
1440 * Generate link setter/getter methods for class definition
1442 * @param string Existing class contents
1446 function _generateLinkMethods($input)
1449 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1452 // only generate if option is set to true
1454 // generate_link_methods true::
1457 if (empty($options['generate_link_methods'])) {
1458 //echo "skip lm? - not set";
1462 if (empty($this->_fkeys)) {
1463 // echo "skip lm? - fkyes empty";
1466 if (empty($this->_fkeys[$this->table])) {
1467 //echo "skip lm? - no fkeys for {$this->table}";
1471 // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1472 $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1475 $defs = $this->_fkeys[$this->table];
1478 // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
1480 // loop through properties and create setter methods
1481 foreach ($defs as $k => $info) {
1483 // build mehtod name
1484 $methodName = is_callable($options['generate_link_methods']) ?
1485 $options['generate_link_methods']($k) : $k;
1487 if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1491 $setters .= " /**\n";
1492 $setters .= " * Getter / Setter for \${$k}\n";
1494 $setters .= " * @param mixed (optional) value to assign\n";
1495 $setters .= " * @access public\n";
1497 $setters .= " */\n";
1498 $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
1500 $setters .= "function $methodName() {\n";
1501 $setters .= " return \$this->link('$k', func_get_args());\n";
1502 $setters .= " }\n\n";
1509 * Generate setter methods for class definition
1511 * @param string Existing class contents
1515 function _generateSetters($input)
1518 $options = &PEAR::getStaticProperty('DB_DataObject','options');
1521 // only generate if option is set to true
1522 if (empty($options['generate_setters'])) {
1526 // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1527 $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1530 $defs = $this->_definitions[$this->table];
1532 // loop through properties and create setter methods
1533 foreach ($defs = $defs as $t) {
1535 // build mehtod name
1536 $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
1538 if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1542 $setters .= " /**\n";
1543 $setters .= " * Setter for \${$t->name}\n";
1545 $setters .= " * @param mixed input value\n";
1546 $setters .= " * @access public\n";
1547 $setters .= " */\n";
1548 $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
1550 $setters .= "function $methodName(\$value) {\n";
1551 $setters .= " \$this->{$t->name} = \$value;\n";
1552 $setters .= " }\n\n";
1559 * Generate table Function - used when generator_no_ini is set.
1561 * @param array table array.
1565 function _generateTableFunction($def)
1567 $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
1570 " function table()\n" .
1574 foreach($def as $k=>$v) {
1576 foreach($defines as $dn) {
1577 if ($v & constant('DB_DATAOBJECT_' . $dn)) {
1578 $str .= ' + DB_DATAOBJECT_' . $dn;
1581 if (strlen($str) > 1) {
1582 $str = substr($str,3); // strip the 0 +
1584 // hopefully addslashes is good enough here!!!
1585 $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
1587 return $ret . " );\n" .
1594 * Generate keys Function - used generator_no_ini is set.
1596 * @param array keys array.
1600 function _generateKeysFunction($def)
1604 " function keys()\n" .
1608 foreach($def as $k=>$type) {
1609 // hopefully addslashes is good enough here!!!
1610 $ret .= '\''.addslashes($k).'\', ';
1612 $ret = preg_replace('#, $#', '', $ret);
1613 return $ret . ");\n" .
1620 * Generate sequenceKey Function - used generator_no_ini is set.
1622 * @param array table and key definition.
1626 function _generateSequenceKeyFunction($def)
1630 // DB_DataObject::debugLevel(5);
1631 global $_DB_DATAOBJECT;
1635 $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
1636 $realkeys = $def['keys'];
1637 $keys = array_keys($realkeys);
1638 $usekey = isset($keys[0]) ? $keys[0] : false;
1639 $table = $def['table'];
1647 $ar = array(false,false,false);
1648 if ($usekey !== false) {
1649 if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
1650 $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
1651 if (strpos($usekey,':') !== false) {
1652 list($usekey,$seqname) = explode(':',$usekey);
1656 if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
1657 ($table[$usekey] & DB_DATAOBJECT_INT) &&
1658 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
1660 // use native sequence keys.
1661 $ar = array($usekey,true,$seqname);
1663 // use generated sequence keys..
1664 if ($table[$usekey] & DB_DATAOBJECT_INT) {
1665 $ar = array($usekey,false,$seqname);
1674 " function sequenceKey() // keyname, use native, native name\n" .
1677 foreach($ar as $v) {
1678 switch (gettype($v)) {
1680 $ret .= ($v ? 'true' : 'false') . ', ';
1684 $ret .= "'" . $v . "', ";
1692 $ret = preg_replace('#, $#', '', $ret);
1693 return $ret . ");\n" .
1698 * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
1699 * Only supports mysql and mysqli ... welcome ideas for more..
1702 * @param array table and key definition.
1706 function _generateDefaultsFunction($table,$defs)
1708 $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1709 if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
1710 return; // cant handle non-mysql introspection for defaults.
1712 $options = PEAR::getStaticProperty('DB_DataObject','options');
1713 $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1714 $method = $db_driver == 'DB' ? 'getAll' : 'queryAll';
1715 $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
1716 $defaults = array();
1717 foreach($res as $ar) {
1718 // this is initially very dumb... -> and it may mess up..
1719 $type = $defs[$ar['Field']];
1723 case (is_null( $ar['Default'])):
1724 $defaults[$ar['Field']] = 'null';
1727 case ($type & DB_DATAOBJECT_DATE):
1728 case ($type & DB_DATAOBJECT_TIME):
1729 case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
1732 case ($type & DB_DATAOBJECT_BOOL):
1733 $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
1737 case ($type & DB_DATAOBJECT_STR):
1738 $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
1742 default: // hopefully eveything else... - numbers etc.
1743 if (!strlen($ar['Default'])) {
1746 if (is_numeric($ar['Default'])) {
1747 $defaults[$ar['Field']] = $ar['Default'];
1752 //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
1754 if (empty($defaults)) {
1759 " function defaults() // column default values \n" .
1762 foreach($defaults as $k=>$v) {
1763 $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
1765 return $ret . " );\n" .