PageRenderTime 70ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/extlib/DB/DataObject/Generator.php

https://github.com/zh/statusnet
PHP | 1568 lines | 905 code | 281 blank | 382 comment | 143 complexity | b88cad42760b2d12489ebea8e9da47dc MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-3.0, GPL-2.0

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

Large files files are truncated, but you can click here to view the full file