PageRenderTime 55ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/extlib/DB/DataObject/Generator.php

https://bitbucket.org/stk2k/charcoalphp2.1
PHP | 1428 lines | 805 code | 267 blank | 356 comment | 133 complexity | f537ba4dd7e205697ca229988a655ec6 MD5 | raw file
  1. <?php
  2. /**
  3. * Generation tools for DB_DataObject
  4. *
  5. * PHP versions 4 and 5
  6. *
  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.
  12. *
  13. * @category Database
  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.122 2006/10/16 02:20:16 alan_k Exp $
  19. * @link http://pear.php.net/package/DB_DataObject
  20. */
  21. /*
  22. * Security Notes:
  23. * This class uses eval to create classes on the fly.
  24. * The table name and database name are used to check the database before writing the
  25. * class definitions, we now check for quotes and semi-colon's in both variables
  26. * so I cant see how it would be possible to generate code even if
  27. * for some crazy reason you took the classname and table name from User Input.
  28. *
  29. * If you consider that wrong, or can prove it.. let me know!
  30. */
  31. /**
  32. *
  33. * Config _$ptions
  34. * [DB_DataObject_Generator]
  35. * ; optional default = DB/DataObject.php
  36. * extends_location =
  37. * ; optional default = DB_DataObject
  38. * extends =
  39. * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
  40. * generator_class_rewrite = ANY|specific_name // default is DB_DataObject
  41. *
  42. */
  43. /**
  44. * Needed classes
  45. * We lazy load here, due to problems with the tests not setting up include path correctly.
  46. * FIXME!
  47. */
  48. class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
  49. //require_once('Config.php');
  50. /**
  51. * Generator class
  52. *
  53. * @package DB_DataObject
  54. */
  55. class DB_DataObject_Generator extends DB_DataObject
  56. {
  57. /* =========================================================== */
  58. /* Utility functions - for building db config files */
  59. /* =========================================================== */
  60. /**
  61. * Array of table names
  62. *
  63. * @var array
  64. * @access private
  65. */
  66. var $tables;
  67. /**
  68. * associative array table -> array of table row objects
  69. *
  70. * @var array
  71. * @access private
  72. */
  73. var $_definitions;
  74. /**
  75. * active table being output
  76. *
  77. * @var string
  78. * @access private
  79. */
  80. var $table; // active tablename
  81. /**
  82. * The 'starter' = call this to start the process
  83. *
  84. * @access public
  85. * @return none
  86. */
  87. function start()
  88. {
  89. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  90. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  91. $databases = array();
  92. foreach($options as $k=>$v) {
  93. if (substr($k,0,9) == 'database_') {
  94. $databases[substr($k,9)] = $v;
  95. }
  96. }
  97. if (isset($options['database'])) {
  98. if ($db_driver == 'DB') {
  99. require_once 'DB.php';
  100. $dsn = DB::parseDSN($options['database']);
  101. } else {
  102. require_once 'MDB2.php';
  103. $dsn = MDB2::parseDSN($options['database']);
  104. }
  105. if (!isset($database[$dsn['database']])) {
  106. $databases[$dsn['database']] = $options['database'];
  107. }
  108. }
  109. foreach($databases as $databasename => $database) {
  110. if (!$database) {
  111. continue;
  112. }
  113. $this->debug("CREATING FOR $databasename\n");
  114. $class = get_class($this);
  115. $t = new $class;
  116. $t->_database_dsn = $database;
  117. $t->_database = $databasename;
  118. if ($db_driver == 'DB') {
  119. require_once 'DB.php';
  120. $dsn = DB::parseDSN($database);
  121. } else {
  122. require_once 'MDB2.php';
  123. $dsn = MDB2::parseDSN($database);
  124. }
  125. if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
  126. $t->_database = basename($t->_database);
  127. }
  128. $t->_createTableList();
  129. foreach(get_class_methods($class) as $method) {
  130. if (substr($method,0,8 ) != 'generate') {
  131. continue;
  132. }
  133. $this->debug("calling $method");
  134. $t->$method();
  135. }
  136. }
  137. $this->debug("DONE\n\n");
  138. }
  139. /**
  140. * Output File was config object, now just string
  141. * Used to generate the Tables
  142. *
  143. * @var string outputbuffer for table definitions
  144. * @access private
  145. */
  146. var $_newConfig;
  147. /**
  148. * Build a list of tables;
  149. * and store it in $this->tables and $this->_definitions[tablename];
  150. *
  151. * @access private
  152. * @return none
  153. */
  154. function _createTableList()
  155. {
  156. $this->_connect();
  157. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  158. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  159. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  160. $is_MDB2 = ($db_driver != 'DB') ? true : false;
  161. if (!$is_MDB2) {
  162. // try getting a list of schema tables first. (postgres)
  163. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  164. $this->tables = $__DB->getListOf('schema.tables');
  165. $__DB->popExpect();
  166. } else {
  167. /**
  168. * set portability and some modules to fetch the informations
  169. */
  170. $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
  171. $__DB->loadModule('Manager');
  172. $__DB->loadModule('Reverse');
  173. }
  174. if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) {
  175. //if that fails fall back to clasic tables list.
  176. if (!$is_MDB2) {
  177. // try getting a list of schema tables first. (postgres)
  178. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  179. $this->tables = $__DB->getListOf('tables');
  180. $__DB->popExpect();
  181. } else {
  182. $this->tables = $__DB->manager->listTables();
  183. $sequences = $__DB->manager->listSequences();
  184. foreach ($sequences as $k => $v) {
  185. $this->tables[] = $__DB->getSequenceName($v);
  186. }
  187. }
  188. }
  189. if (is_a($this->tables , 'PEAR_Error')) {
  190. return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
  191. }
  192. // build views as well if asked to.
  193. if (!empty($options['build_views'])) {
  194. if (!$is_MDB2) {
  195. $views = $__DB->getListOf('views');
  196. } else {
  197. $views = $__DB->manager->listViews();
  198. }
  199. if (is_a($views,'PEAR_Error')) {
  200. return PEAR::raiseError(
  201. 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
  202. $views->toString(),
  203. null,
  204. PEAR_ERROR_DIE
  205. );
  206. }
  207. $this->tables = array_merge ($this->tables, $views);
  208. }
  209. // declare a temporary table to be filled with matching tables names
  210. $tmp_table = array();
  211. foreach($this->tables as $table) {
  212. if (isset($options['generator_include_regex']) &&
  213. !preg_match($options['generator_include_regex'],$table)) {
  214. continue;
  215. } else if (isset($options['generator_exclude_regex']) &&
  216. preg_match($options['generator_exclude_regex'],$table)) {
  217. continue;
  218. }
  219. // postgres strip the schema bit from the
  220. if (!empty($options['generator_strip_schema'])) {
  221. $bits = explode('.', $table,2);
  222. $table = $bits[0];
  223. if (count($bits) > 1) {
  224. $table = $bits[1];
  225. }
  226. }
  227. if (!$is_MDB2) {
  228. $defs = $__DB->tableInfo($table);
  229. } else {
  230. $defs = $__DB->reverse->tableInfo($table);
  231. // rename the length value, so it matches db's return.
  232. foreach ($defs as $k => $v) {
  233. $defs[$k]['len'] = $defs[$k]['length'];
  234. }
  235. }
  236. if (is_a($defs,'PEAR_Error')) {
  237. // running in debug mode should pick this up as a big warning..
  238. $this->raiseError('Error reading tableInfo, '. $defs->toString());
  239. continue;
  240. }
  241. // cast all definitions to objects - as we deal with that better.
  242. foreach($defs as $def) {
  243. if (!is_array($def)) {
  244. continue;
  245. }
  246. $this->_definitions[$table][] = (object) $def;
  247. }
  248. // we find a matching table, just store it into a temporary array
  249. $tmp_table[] = $table;
  250. }
  251. // the temporary table array is now the right one (tables names matching
  252. // with regex expressions have been removed)
  253. $this->tables = $tmp_table;
  254. //print_r($this->_definitions);
  255. }
  256. /**
  257. * Auto generation of table data.
  258. *
  259. * it will output to db_oo_{database} the table definitions
  260. *
  261. * @access private
  262. * @return none
  263. */
  264. function generateDefinitions()
  265. {
  266. $this->debug("Generating Definitions file: ");
  267. if (!$this->tables) {
  268. $this->debug("-- NO TABLES -- \n");
  269. return;
  270. }
  271. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  272. //$this->_newConfig = new Config('IniFile');
  273. $this->_newConfig = '';
  274. foreach($this->tables as $this->table) {
  275. $this->_generateDefinitionsTable();
  276. }
  277. $this->_connect();
  278. // dont generate a schema if location is not set
  279. // it's created on the fly!
  280. if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
  281. return;
  282. }
  283. if (!empty($options['generator_no_ini'])) { // built in ini files..
  284. return;
  285. }
  286. $base = @$options['schema_location'];
  287. if (isset($options["ini_{$this->_database}"])) {
  288. $file = $options["ini_{$this->_database}"];
  289. } else {
  290. $file = "{$base}/{$this->_database}.ini";
  291. }
  292. if (!file_exists(dirname($file))) {
  293. require_once 'System.php';
  294. System::mkdir(array('-p','-m',0755,dirname($file)));
  295. }
  296. $this->debug("Writing ini as {$file}\n");
  297. touch($file);
  298. //print_r($this->_newConfig);
  299. $fh = fopen($file,'w');
  300. fwrite($fh,$this->_newConfig);
  301. fclose($fh);
  302. //$ret = $this->_newConfig->writeInput($file,false);
  303. //if (PEAR::isError($ret) ) {
  304. // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
  305. // }
  306. }
  307. /**
  308. * generate Foreign Keys (for links.ini)
  309. * Currenly only works with mysql / mysqli
  310. * to use, you must set option: generate_links=true
  311. *
  312. * @author Pascal Sch?&#x192;?i
  313. */
  314. function generateForeignKeys()
  315. {
  316. $options = PEAR::getStaticProperty('DB_DataObject','options');
  317. if (empty($options['generate_links'])) {
  318. return false;
  319. }
  320. $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  321. if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
  322. echo "WARNING: cant handle non-mysql introspection for defaults.";
  323. return; // cant handle non-mysql introspection for defaults.
  324. }
  325. $DB = $this->getDatabaseConnection();
  326. $fk = array();
  327. foreach($this->tables as $this->table) {
  328. $res =& $DB->query('SHOW CREATE TABLE ' . $this->table);
  329. if (PEAR::isError($res)) {
  330. die($res->getMessage());
  331. }
  332. $text = $res->fetchRow(DB_DEFAULT_MODE, 0);
  333. $treffer = array();
  334. // Extract FOREIGN KEYS
  335. preg_match_all(
  336. "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
  337. $text[1],
  338. $treffer,
  339. PREG_SET_ORDER);
  340. if (count($treffer) < 1) {
  341. continue;
  342. }
  343. for ($i = 0; $i < count($treffer); $i++) {
  344. $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3];
  345. }
  346. }
  347. $links_ini = "";
  348. foreach($fk as $table => $details) {
  349. $links_ini .= "[$table]\n";
  350. foreach ($details as $col => $ref) {
  351. $links_ini .= "$col = $ref\n";
  352. }
  353. $links_ini .= "\n";
  354. }
  355. // dont generate a schema if location is not set
  356. // it's created on the fly!
  357. $options = PEAR::getStaticProperty('DB_DataObject','options');
  358. if (empty($options['schema_location'])) {
  359. return;
  360. }
  361. $file = "{$options['schema_location']}/{$this->_database}.links.ini";
  362. if (!file_exists(dirname($file))) {
  363. require_once 'System.php';
  364. System::mkdir(array('-p','-m',0755,dirname($file)));
  365. }
  366. $this->debug("Writing ini as {$file}\n");
  367. touch($file); // not sure why this is needed?
  368. $fh = fopen($file,'w');
  369. fwrite($fh,$links_ini);
  370. fclose($fh);
  371. }
  372. /**
  373. * The table geneation part
  374. *
  375. * @access private
  376. * @return tabledef and keys array.
  377. */
  378. function _generateDefinitionsTable()
  379. {
  380. global $_DB_DATAOBJECT;
  381. $defs = $this->_definitions[$this->table];
  382. $this->_newConfig .= "\n[{$this->table}]\n";
  383. $keys_out = "\n[{$this->table}__keys]\n";
  384. $keys_out_primary = '';
  385. $keys_out_secondary = '';
  386. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  387. echo "TABLE STRUCTURE FOR {$this->table}\n";
  388. print_r($defs);
  389. }
  390. $DB = $this->getDatabaseConnection();
  391. $dbtype = $DB->phptype;
  392. $ret = array(
  393. 'table' => array(),
  394. 'keys' => array(),
  395. );
  396. $ret_keys_primary = array();
  397. $ret_keys_secondary = array();
  398. foreach($defs as $t) {
  399. $n=0;
  400. $write_ini = true;
  401. switch (strtoupper($t->type)) {
  402. case 'INT':
  403. case 'INT2': // postgres
  404. case 'INT4': // postgres
  405. case 'INT8': // postgres
  406. case 'SERIAL4': // postgres
  407. case 'SERIAL8': // postgres
  408. case 'INTEGER':
  409. case 'TINYINT':
  410. case 'SMALLINT':
  411. case 'MEDIUMINT':
  412. case 'BIGINT':
  413. $type = DB_DATAOBJECT_INT;
  414. if ($t->len == 1) {
  415. $type += DB_DATAOBJECT_BOOL;
  416. }
  417. break;
  418. case 'REAL':
  419. case 'DOUBLE':
  420. case 'DOUBLE PRECISION': // double precision (firebird)
  421. case 'FLOAT':
  422. case 'FLOAT4': // real (postgres)
  423. case 'FLOAT8': // double precision (postgres)
  424. case 'DECIMAL':
  425. case 'MONEY': // mssql and maybe others
  426. case 'NUMERIC':
  427. case 'NUMBER': // oci8
  428. $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
  429. break;
  430. case 'YEAR':
  431. $type = DB_DATAOBJECT_INT;
  432. break;
  433. case 'BIT':
  434. case 'BOOL':
  435. case 'BOOLEAN':
  436. $type = DB_DATAOBJECT_BOOL;
  437. // postgres needs to quote '0'
  438. if ($dbtype == 'pgsql') {
  439. $type += DB_DATAOBJECT_STR;
  440. }
  441. break;
  442. case 'STRING':
  443. case 'CHAR':
  444. case 'VARCHAR':
  445. case 'VARCHAR2':
  446. case 'TINYTEXT':
  447. case 'ENUM':
  448. case 'SET': // not really but oh well
  449. case 'TIMESTAMPTZ': // postgres
  450. case 'BPCHAR': // postgres
  451. case 'INTERVAL': // postgres (eg. '12 days')
  452. case 'CIDR': // postgres IP net spec
  453. case 'INET': // postgres IP
  454. case 'MACADDR': // postgress network Mac address.
  455. case 'INTEGER[]': // postgres type
  456. case 'BOOLEAN[]': // postgres type
  457. $type = DB_DATAOBJECT_STR;
  458. break;
  459. case 'TEXT':
  460. case 'MEDIUMTEXT':
  461. case 'LONGTEXT':
  462. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
  463. break;
  464. case 'DATE':
  465. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
  466. break;
  467. case 'TIME':
  468. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
  469. break;
  470. case 'DATETIME':
  471. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
  472. break;
  473. case 'TIMESTAMP': // do other databases use this???
  474. $type = ($dbtype == 'mysql') ?
  475. DB_DATAOBJECT_MYSQLTIMESTAMP :
  476. DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
  477. break;
  478. case 'TINYBLOB':
  479. case 'BLOB': /// these should really be ignored!!!???
  480. case 'MEDIUMBLOB':
  481. case 'LONGBLOB':
  482. case 'BYTEA': // postgres blob support..
  483. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
  484. break;
  485. default:
  486. echo "*****************************************************************\n".
  487. "** WARNING UNKNOWN TYPE **\n".
  488. "** Found column '{$t->name}', of type '{$t->type}' **\n".
  489. "** Please submit a bug, describe what type you expect this **\n".
  490. "** column to be **\n".
  491. "*****************************************************************\n";
  492. $write_ini = false;
  493. break;
  494. }
  495. if (!strlen(trim($t->name))) {
  496. continue;
  497. }
  498. if (preg_match('/not[ _]null/i',$t->flags)) {
  499. $type += DB_DATAOBJECT_NOTNULL;
  500. }
  501. if (in_array($t->name,array('null','yes','no','true','false'))) {
  502. echo "*****************************************************************\n".
  503. "** WARNING **\n".
  504. "** Found column '{$t->name}', which is invalid in an .ini file **\n".
  505. "** This line will not be writen to the file - you will have **\n".
  506. "** define the keys()/method manually. **\n".
  507. "*****************************************************************\n";
  508. $write_ini = false;
  509. } else {
  510. $this->_newConfig .= "{$t->name} = $type\n";
  511. }
  512. $ret['table'][$t->name] = $type;
  513. // i've no idea if this will work well on other databases?
  514. // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
  515. // if no keys exist fall back to using unique
  516. //echo "\n{$t->name} => {$t->flags}\n";
  517. if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags))
  518. || (isset($t->autoincrement) && ($t->autoincrement === true))) {
  519. // native sequences = 2
  520. if ($write_ini) {
  521. $keys_out_primary .= "{$t->name} = N\n";
  522. }
  523. $ret_keys_primary[$t->name] = 'N';
  524. } else if (preg_match("/(primary|unique)/i",$t->flags)) {
  525. // keys.. = 1
  526. $key_type = 'K';
  527. if (!preg_match("/(primary)/i",$t->flags)) {
  528. $key_type = 'U';
  529. }
  530. if ($write_ini) {
  531. $keys_out_secondary .= "{$t->name} = {$key_type}\n";
  532. }
  533. $ret_keys_secondary[$t->name] = $key_type;
  534. }
  535. }
  536. $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
  537. $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
  538. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  539. print_r(array("dump for {$this->table}", $ret));
  540. }
  541. return $ret;
  542. }
  543. /**
  544. * Convert a table name into a class name -> override this if you want a different mapping
  545. *
  546. * @access public
  547. * @return string class name;
  548. */
  549. function getClassNameFromTableName($table)
  550. {
  551. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  552. $class_prefix = $options['class_prefix'];
  553. return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
  554. }
  555. /**
  556. * Convert a table name into a file name -> override this if you want a different mapping
  557. *
  558. * @access public
  559. * @return string file name;
  560. */
  561. function getFileNameFromTableName($table)
  562. {
  563. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  564. $base = $options['class_location'];
  565. if (strpos($base,'%s') !== false) {
  566. $base = dirname($base);
  567. }
  568. if (!file_exists($base)) {
  569. require_once 'System.php';
  570. System::mkdir(array('-p',$base));
  571. }
  572. if (strpos($options['class_location'],'%s') !== false) {
  573. $outfilename = sprintf($options['class_location'],
  574. preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
  575. } else {
  576. $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
  577. }
  578. return $outfilename;
  579. }
  580. /**
  581. * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
  582. *
  583. * @access public
  584. * @return string method name;
  585. */
  586. function getMethodNameFromColumnName($col)
  587. {
  588. return ucfirst($col);
  589. }
  590. /*
  591. * building the class files
  592. * for each of the tables output a file!
  593. */
  594. function generateClasses()
  595. {
  596. //echo "Generating Class files: \n";
  597. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  598. if ($extends = @$options['extends']) {
  599. $this->_extends = $extends;
  600. $this->_extendsFile = $options['extends_location'];
  601. }
  602. foreach($this->tables as $this->table) {
  603. $this->table = trim($this->table);
  604. $this->classname = $this->getClassNameFromTableName($this->table);
  605. $i = '';
  606. $outfilename = $this->getFileNameFromTableName($this->table);
  607. $oldcontents = '';
  608. if (file_exists($outfilename)) {
  609. // file_get_contents???
  610. $oldcontents = implode('',file($outfilename));
  611. }
  612. $out = $this->_generateClassTable($oldcontents);
  613. $this->debug( "writing $this->classname\n");
  614. $fh = fopen($outfilename, "w");
  615. fputs($fh,$out);
  616. fclose($fh);
  617. }
  618. //echo $out;
  619. }
  620. /**
  621. * class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx
  622. *
  623. * @var string
  624. * @access private
  625. */
  626. var $_extends = 'DB_DataObject';
  627. /**
  628. * line to use for require('DB/DataObject.php');
  629. *
  630. * @var string
  631. * @access private
  632. */
  633. var $_extendsFile = "DB/DataObject.php";
  634. /**
  635. * class being generated
  636. *
  637. * @var string
  638. * @access private
  639. */
  640. var $_className;
  641. /**
  642. * The table class geneation part - single file.
  643. *
  644. * @access private
  645. * @return none
  646. */
  647. function _generateClassTable($input = '')
  648. {
  649. // title = expand me!
  650. $foot = "";
  651. $head = "<?php\n/**\n * Table Definition for {$this->table}\n */\n";
  652. // requires
  653. $head .= "require_once '{$this->_extendsFile}';\n\n";
  654. // add dummy class header in...
  655. // class
  656. $head .= "class {$this->classname} extends {$this->_extends} \n{";
  657. $body = "\n ###START_AUTOCODE\n";
  658. $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
  659. // table
  660. $padding = (30 - strlen($this->table));
  661. $padding = ($padding < 2) ? 2 : $padding;
  662. $p = str_repeat(' ',$padding) ;
  663. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  664. $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
  665. $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
  666. $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
  667. // if we are using the option database_{databasename} = dsn
  668. // then we should add var $_database = here
  669. // as database names may not always match..
  670. if (isset($options["database_{$this->_database}"])) {
  671. $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
  672. }
  673. if (!empty($options['generator_novars'])) {
  674. $var = '//'.$var;
  675. }
  676. $defs = $this->_definitions[$this->table];
  677. // show nice information!
  678. $connections = array();
  679. $sets = array();
  680. foreach($defs as $t) {
  681. if (!strlen(trim($t->name))) {
  682. continue;
  683. }
  684. $padding = (30 - strlen($t->name));
  685. if ($padding < 2) $padding =2;
  686. $p = str_repeat(' ',$padding) ;
  687. $body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->flags}\n";
  688. // can not do set as PEAR::DB table info doesnt support it.
  689. //if (substr($t->Type,0,3) == "set")
  690. // $sets[$t->Field] = "array".substr($t->Type,3);
  691. $body .= $this->derivedHookVar($t,$padding);
  692. }
  693. // THIS IS TOTALLY BORKED old FC creation
  694. // IT WILL BE REMOVED!!!!! in DataObjects 1.6
  695. // grep -r __clone * to find all it's uses
  696. // and replace them with $x = clone($y);
  697. // due to the change in the PHP5 clone design.
  698. if ( substr(phpversion(),0,1) < 5) {
  699. $body .= "\n";
  700. $body .= " /* ZE2 compatibility trick*/\n";
  701. $body .= " function __clone() { return \$this;}\n";
  702. }
  703. // simple creation tools ! (static stuff!)
  704. $body .= "\n";
  705. $body .= " /* Static get */\n";
  706. $body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
  707. // generate getter and setter methods
  708. $body .= $this->_generateGetters($input);
  709. $body .= $this->_generateSetters($input);
  710. /*
  711. theoretically there is scope here to introduce 'list' methods
  712. based up 'xxxx_up' column!!! for heiracitcal trees..
  713. */
  714. // set methods
  715. //foreach ($sets as $k=>$v) {
  716. // $kk = strtoupper($k);
  717. // $body .=" function getSets{$k}() { return {$v}; }\n";
  718. //}
  719. if (!empty($options['generator_no_ini'])) {
  720. $def = $this->_generateDefinitionsTable(); // simplify this!?
  721. $body .= $this->_generateTableFunction($def['table']);
  722. $body .= $this->_generateKeysFunction($def['keys']);
  723. $body .= $this->_generateSequenceKeyFunction($def);
  724. $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
  725. } else if (!empty($options['generator_add_defaults'])) {
  726. // I dont really like doing it this way (adding another option)
  727. // but it helps on older projects.
  728. $def = $this->_generateDefinitionsTable(); // simplify this!?
  729. $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
  730. }
  731. $body .= $this->derivedHookFunctions($input);
  732. $body .= "\n /* the code above is auto generated do not remove the tag below */";
  733. $body .= "\n ###END_AUTOCODE\n";
  734. // stubs..
  735. if (!empty($options['generator_add_validate_stubs'])) {
  736. foreach($defs as $t) {
  737. if (!strlen(trim($t->name))) {
  738. continue;
  739. }
  740. $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
  741. // dont re-add it..
  742. if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
  743. continue;
  744. }
  745. $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
  746. }
  747. }
  748. $foot .= "}\n";
  749. $full = $head . $body . $foot;
  750. if (!$input) {
  751. return $full;
  752. }
  753. if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
  754. return $full;
  755. }
  756. if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
  757. return $full;
  758. }
  759. /* this will only replace extends DB_DataObject by default,
  760. unless use set generator_class_rewrite to ANY or a name*/
  761. $class_rewrite = 'DB_DataObject';
  762. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  763. if (!($class_rewrite = @$options['generator_class_rewrite'])) {
  764. $class_rewrite = 'DB_DataObject';
  765. }
  766. if ($class_rewrite == 'ANY') {
  767. $class_rewrite = '[a-z_]+';
  768. }
  769. $input = preg_replace(
  770. '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*\{(\n|\r\n)/si',
  771. "\nclass {$this->classname} extends {$this->_extends} \n{\n",
  772. $input);
  773. return preg_replace(
  774. '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
  775. $body,$input);
  776. }
  777. /**
  778. * hook to add extra methods to all classes
  779. *
  780. * called once for each class, use with $this->table and
  781. * $this->_definitions[$this->table], to get data out of the current table,
  782. * use it to add extra methods to the default classes.
  783. *
  784. * @access public
  785. * @return string added to class eg. functions.
  786. */
  787. function derivedHookFunctions($input = "")
  788. {
  789. // This is so derived generator classes can generate functions
  790. // It MUST NOT be changed here!!!
  791. return "";
  792. }
  793. /**
  794. * hook for var lines
  795. * called each time a var line is generated, override to add extra var
  796. * lines
  797. *
  798. * @param object t containing type,len,flags etc. from tableInfo call
  799. * @param int padding number of spaces
  800. * @access public
  801. * @return string added to class eg. functions.
  802. */
  803. function derivedHookVar(&$t,$padding)
  804. {
  805. // This is so derived generator classes can generate variabels
  806. // It MUST NOT be changed here!!!
  807. return "";
  808. }
  809. /**
  810. * getProxyFull - create a class definition on the fly and instantate it..
  811. *
  812. * similar to generated files - but also evals the class definitoin code.
  813. *
  814. *
  815. * @param string database name
  816. * @param string table name of table to create proxy for.
  817. *
  818. *
  819. * @return object Instance of class. or PEAR Error
  820. * @access public
  821. */
  822. function getProxyFull($database,$table)
  823. {
  824. if ($err = $this->fillTableSchema($database,$table)) {
  825. return $err;
  826. }
  827. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  828. $class_prefix = $options['class_prefix'];
  829. if ($extends = @$options['extends']) {
  830. $this->_extends = $extends;
  831. $this->_extendsFile = $options['extends_location'];
  832. }
  833. $classname = $this->classname = $this->getClassNameFromTableName($this->table);
  834. $out = $this->_generateClassTable();
  835. //echo $out;
  836. eval('?>'.$out);
  837. return new $classname;
  838. }
  839. /**
  840. * fillTableSchema - set the database schema on the fly
  841. *
  842. *
  843. *
  844. * @param string database name
  845. * @param string table name of table to create schema info for
  846. *
  847. * @return none | PEAR::error()
  848. * @access public
  849. */
  850. function fillTableSchema($database,$table)
  851. {
  852. global $_DB_DATAOBJECT;
  853. // a little bit of sanity testing.
  854. if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {
  855. return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
  856. }
  857. $this->_database = $database;
  858. $this->_connect();
  859. $table = trim($table);
  860. // a little bit of sanity testing.
  861. if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {
  862. return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
  863. }
  864. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  865. $options = PEAR::getStaticProperty('DB_DataObject','options');
  866. $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
  867. $is_MDB2 = ($db_driver != 'DB') ? true : false;
  868. if (!$is_MDB2) {
  869. // try getting a list of schema tables first. (postgres)
  870. $__DB->expectError(DB_ERROR_UNSUPPORTED);
  871. $this->tables = $__DB->getListOf('schema.tables');
  872. $__DB->popExpect();
  873. } else {
  874. /**
  875. * set portability and some modules to fetch the informations
  876. */
  877. $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
  878. $__DB->loadModule('Manager');
  879. $__DB->loadModule('Reverse');
  880. }
  881. if (!$is_MDB2) {
  882. $defs = $__DB->tableInfo($table);
  883. } else {
  884. $defs = $__DB->reverse->tableInfo($table);
  885. foreach ($defs as $k => $v) {
  886. $defs[$k]['len'] = &$defs[$k]['length'];
  887. }
  888. }
  889. if (PEAR::isError($defs)) {
  890. return $defs;
  891. }
  892. if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
  893. $this->debug("getting def for $database/$table",'fillTable');
  894. $this->debug(print_r($defs,true),'defs');
  895. }
  896. // cast all definitions to objects - as we deal with that better.
  897. foreach($defs as $def) {
  898. if (is_array($def)) {
  899. $this->_definitions[$table][] = (object) $def;
  900. }
  901. }
  902. $this->table = trim($table);
  903. $ret = $this->_generateDefinitionsTable();
  904. $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
  905. $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
  906. return false;
  907. }
  908. /**
  909. * Generate getter methods for class definition
  910. *
  911. * @param string $input Existing class contents
  912. * @return string
  913. * @access public
  914. */
  915. function _generateGetters($input)
  916. {
  917. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  918. $getters = '';
  919. // only generate if option is set to true
  920. if (empty($options['generate_getters'])) {
  921. return '';
  922. }
  923. // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
  924. $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
  925. $getters .= "\n\n";
  926. $defs = $this->_definitions[$this->table];
  927. // loop through properties and create getter methods
  928. foreach ($defs = $defs as $t) {
  929. // build mehtod name
  930. $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
  931. if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
  932. continue;
  933. }
  934. $getters .= " /**\n";
  935. $getters .= " * Getter for \${$t->name}\n";
  936. $getters .= " *\n";
  937. $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
  938. : " * @return {$t->type}\n";
  939. $getters .= " * @access public\n";
  940. $getters .= " */\n";
  941. $getters .= (substr(phpversion(),0,1) > 4) ? ' public '
  942. : ' ';
  943. $getters .= "function $methodName() {\n";
  944. $getters .= " return \$this->{$t->name};\n";
  945. $getters .= " }\n\n";
  946. }
  947. return $getters;
  948. }
  949. /**
  950. * Generate setter methods for class definition
  951. *
  952. * @param string Existing class contents
  953. * @return string
  954. * @access public
  955. */
  956. function _generateSetters($input)
  957. {
  958. $options = &PEAR::getStaticProperty('DB_DataObject','options');
  959. $setters = '';
  960. // only generate if option is set to true
  961. if (empty($options['generate_setters'])) {
  962. return '';
  963. }
  964. // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
  965. $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
  966. $setters .= "\n";
  967. $defs = $this->_definitions[$this->table];
  968. // loop through properties and create setter methods
  969. foreach ($defs = $defs as $t) {
  970. // build mehtod name
  971. $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
  972. if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
  973. continue;
  974. }
  975. $setters .= " /**\n";
  976. $setters .= " * Setter for \${$t->name}\n";
  977. $setters .= " *\n";
  978. $setters .= " * @param mixed input value\n";
  979. $setters .= " * @access public\n";
  980. $setters .= " */\n";
  981. $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
  982. : ' ';
  983. $setters .= "function $methodName(\$value) {\n";
  984. $setters .= " \$this->{$t->name} = \$value;\n";
  985. $setters .= " }\n\n";
  986. }
  987. return $setters;
  988. }
  989. /**
  990. * Generate table Function - used when generator_no_ini is set.
  991. *
  992. * @param array table array.
  993. * @return string
  994. * @access public
  995. */
  996. function _generateTableFunction($def)
  997. {
  998. $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
  999. $ret = "\n" .
  1000. " function table()\n" .
  1001. " {\n" .
  1002. " return array(\n";
  1003. foreach($def as $k=>$v) {
  1004. $str = '0';
  1005. foreach($defines as $dn) {
  1006. if ($v & constant('DB_DATAOBJECT_' . $dn)) {
  1007. $str .= ' + DB_DATAOBJECT_' . $dn;
  1008. }
  1009. }
  1010. if (strlen($str) > 1) {
  1011. $str = substr($str,3); // strip the 0 +
  1012. }
  1013. // hopefully addslashes is good enough here!!!
  1014. $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
  1015. }
  1016. return $ret . " );\n" .
  1017. " }\n";
  1018. }
  1019. /**
  1020. * Generate keys Function - used generator_no_ini is set.
  1021. *
  1022. * @param array keys array.
  1023. * @return string
  1024. * @access public
  1025. */
  1026. function _generateKeysFunction($def)
  1027. {
  1028. $ret = "\n" .
  1029. " function keys()\n" .
  1030. " {\n" .
  1031. " return array(";
  1032. foreach($def as $k=>$type) {
  1033. // hopefully addslashes is good enough here!!!
  1034. $ret .= '\''.addslashes($k).'\', ';
  1035. }
  1036. $ret = preg_replace('#, $#', '', $ret);
  1037. return $ret . ");\n" .
  1038. " }\n";
  1039. }
  1040. /**
  1041. * Generate sequenceKey Function - used generator_no_ini is set.
  1042. *
  1043. * @param array table and key definition.
  1044. * @return string
  1045. * @access public
  1046. */
  1047. function _generateSequenceKeyFunction($def)
  1048. {
  1049. //print_r($def);
  1050. // DB_DataObject::debugLevel(5);
  1051. global $_DB_DATAOBJECT;
  1052. // print_r($def);
  1053. $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  1054. $realkeys = $def['keys'];
  1055. $keys = array_keys($realkeys);
  1056. $usekey = isset($keys[0]) ? $keys[0] : false;
  1057. $table = $def['table'];
  1058. $seqname = false;
  1059. $ar = array(false,false,false);
  1060. if ($usekey !== false) {
  1061. if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
  1062. $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
  1063. if (strpos($usekey,':') !== false) {
  1064. list($usekey,$seqname) = explode(':',$usekey);
  1065. }
  1066. }
  1067. if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
  1068. ($table[$usekey] & DB_DATAOBJECT_INT) &&
  1069. isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
  1070. ) {
  1071. // use native sequence keys.
  1072. $ar = array($usekey,true,$seqname);
  1073. } else {
  1074. // use generated sequence keys..
  1075. if ($table[$usekey] & DB_DATAOBJECT_INT) {
  1076. $ar = array($usekey,false,$seqname);
  1077. }
  1078. }
  1079. }
  1080. $ret = "\n" .
  1081. " function sequenceKey() // keyname, use native, native name\n" .
  1082. " {\n" .
  1083. " return array(";
  1084. foreach($ar as $v) {
  1085. switch (gettype($v)) {
  1086. case 'boolean':
  1087. $ret .= ($v ? 'true' : 'false') . ', ';
  1088. break;
  1089. case 'string':
  1090. $ret .= "'" . $v . "', ";
  1091. break;
  1092. default: // eak
  1093. $ret .= "null, ";
  1094. }
  1095. }
  1096. $ret = preg_replace('#, $#', '', $ret);
  1097. return $ret . ");\n" .
  1098. " }\n";
  1099. }
  1100. /**
  1101. * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
  1102. * Only supports mysql and mysqli ... welcome ideas for more..
  1103. *
  1104. *
  1105. * @param array table and key definition.
  1106. * @return string
  1107. * @access public
  1108. */
  1109. function _generateDefaultsFunction($table,$defs)
  1110. {
  1111. $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
  1112. if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
  1113. return; // cant handle non-mysql introspection for defaults.
  1114. }
  1115. $res = $__DB->getAll('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
  1116. $defaults = array();
  1117. foreach($res as $ar) {
  1118. // this is initially very dumb... -> and it may mess up..
  1119. $type = $defs[$ar['Field']];
  1120. switch (true) {
  1121. case (is_null( $ar['Default'])):
  1122. $defaults[$ar['Field']] = 'null';
  1123. break;
  1124. case ($type & DB_DATAOBJECT_DATE):
  1125. case ($type & DB_DATAOBJECT_TIME):
  1126. case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
  1127. break;
  1128. case ($type & DB_DATAOBJECT_BOOL):
  1129. $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
  1130. break;
  1131. case ($type & DB_DATAOBJECT_STR):
  1132. $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
  1133. break;
  1134. default: // hopefully eveything else... - numbers etc.
  1135. if (!strlen($ar['Default'])) {
  1136. continue;
  1137. }
  1138. if (is_numeric($ar['Default'])) {
  1139. $defaults[$ar['Field']] = $ar['Default'];
  1140. }
  1141. break;
  1142. }
  1143. //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
  1144. }
  1145. if (empty($defaults)) {
  1146. return;
  1147. }
  1148. $ret = "\n" .
  1149. " function defaults() // column default values \n" .
  1150. " {\n" .
  1151. " return array(\n";
  1152. foreach($defaults as $k=>$v) {
  1153. $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
  1154. }
  1155. return $ret . " );\n" .
  1156. " }\n";
  1157. }
  1158. }