Safe_DataObject.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /*
  17. * @copyright 2010 StatusNet, Inc.
  18. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  19. */
  20. defined('GNUSOCIAL') || die();
  21. /**
  22. * Extended DB_DataObject to improve a few things:
  23. * - free global resources from destructor
  24. * - remove bogus global references from serialized objects
  25. * - don't leak memory when loading already-used .ini files
  26. * (eg when using the same schema on thousands of databases)
  27. */
  28. class Safe_DataObject extends GS_DataObject
  29. {
  30. /**
  31. * Destructor to free global memory resources associated with
  32. * this data object when it's unset or goes out of scope.
  33. * DB_DataObject doesn't do this yet by itself.
  34. */
  35. public function __destruct()
  36. {
  37. $this->free();
  38. if (method_exists('DB_DataObject', '__destruct')) {
  39. parent::__destruct();
  40. }
  41. }
  42. /**
  43. * Magic function called at clone() time.
  44. *
  45. * We use this to drop connection with some global resources.
  46. * This supports the fairly common pattern where individual
  47. * items being read in a loop via a single object are cloned
  48. * for individual processing, then fall out of scope when the
  49. * loop comes around again.
  50. *
  51. * As that triggers the destructor, we want to make sure that
  52. * the original object doesn't have its database result killed.
  53. * It will still be freed properly when the original object
  54. * gets destroyed.
  55. */
  56. public function __clone()
  57. {
  58. $this->_DB_resultid = false;
  59. }
  60. /**
  61. * Magic function called at serialize() time.
  62. *
  63. * We use this to drop a couple process-specific references
  64. * from DB_DataObject which can cause trouble in future
  65. * processes.
  66. *
  67. * @return array of variable names to include in serialization.
  68. */
  69. public function __sleep()
  70. {
  71. $vars = array_keys(get_object_vars($this));
  72. $skip = array('_DB_resultid', '_link_loaded');
  73. return array_diff($vars, $skip);
  74. }
  75. /**
  76. * Magic function called at unserialize() time.
  77. *
  78. * Clean out some process-specific variables which might
  79. * be floating around from a previous process's cached
  80. * objects.
  81. *
  82. * Old cached objects may still have them.
  83. */
  84. public function __wakeup()
  85. {
  86. // Refers to global state info from a previous process.
  87. // Clear this out so we don't accidentally break global
  88. // state in *this* process.
  89. $this->_DB_resultid = null;
  90. // We don't have any local DBO refs, so clear these out.
  91. $this->_link_loaded = false;
  92. }
  93. /**
  94. * Magic function called when someone attempts to call a method
  95. * that doesn't exist. DB_DataObject uses this to implement
  96. * setters and getters for fields, but neglects to throw an error
  97. * when you just misspell an actual method name. This leads to
  98. * silent failures which can cause all kinds of havoc.
  99. *
  100. * @param string $method
  101. * @param array $params
  102. * @return mixed
  103. * @throws Exception
  104. */
  105. public function __call($method, $params)
  106. {
  107. $return = null;
  108. // Yes, that's _call with one underscore, which does the
  109. // actual implementation.
  110. if ($this->_call($method, $params, $return)) {
  111. return $return;
  112. } else {
  113. // Low level exception. No need for i18n as discussed with Brion.
  114. throw new Exception('Call to undefined method ' .
  115. get_class($this) . '::' . $method);
  116. }
  117. }
  118. /**
  119. * Work around memory-leak bugs...
  120. * Had to copy-paste the whole function in order to patch a couple lines of it.
  121. * Would be nice if this code was better factored.
  122. *
  123. * @param optional string name of database to assign / read
  124. * @param optional array structure of database, and keys
  125. * @param optional array table links
  126. *
  127. * @access public
  128. * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
  129. * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
  130. */
  131. public function databaseStructure()
  132. {
  133. global $_DB_DATAOBJECT;
  134. if (!empty($args = func_get_args())) {
  135. if (count($args) == 1) {
  136. // this returns all the tables and their structure..
  137. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  138. $this->debug(
  139. 'Loading Generator as databaseStructure called with args',
  140. 1
  141. );
  142. }
  143. $x = new DB_DataObject;
  144. $x->_database = $args[0];
  145. $this->_connect();
  146. $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  147. $tables = $DB->getListOf('tables');
  148. class_exists('DB_DataObject_Generator') ? '' :
  149. require_once 'DB/DataObject/Generator.php';
  150. foreach ($tables as $table) {
  151. $y = new DB_DataObject_Generator;
  152. $y->fillTableSchema($x->_database, $table);
  153. }
  154. return $_DB_DATAOBJECT['INI'][$x->_database];
  155. } else {
  156. $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
  157. $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
  158. if (isset($args[1])) {
  159. $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
  160. $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
  161. }
  162. return true;
  163. }
  164. }
  165. if (!$this->_database) {
  166. $this->_connect();
  167. }
  168. // loaded already?
  169. if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
  170. // database loaded - but this is table is not available..
  171. if (
  172. empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])
  173. && !empty($_DB_DATAOBJECT['CONFIG']['proxy'])
  174. ) {
  175. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  176. $this->debug('Loading Generator to fetch Schema', 1);
  177. }
  178. class_exists('DB_DataObject_Generator') ? '' :
  179. require_once 'DB/DataObject/Generator.php';
  180. $x = new DB_DataObject_Generator;
  181. $x->fillTableSchema($this->_database, $this->tableName());
  182. }
  183. return true;
  184. }
  185. if (empty($_DB_DATAOBJECT['CONFIG'])) {
  186. self::_loadConfig();
  187. }
  188. // if you supply this with arguments, then it will take those
  189. // as the database and links array...
  190. $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  191. array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  192. array() ;
  193. if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  194. $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  195. $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  196. explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  197. }
  198. /* BEGIN CHANGED FROM UPSTREAM */
  199. $_DB_DATAOBJECT['INI'][$this->_database] = $this->parseIniFiles($schemas);
  200. /* END CHANGED FROM UPSTREAM */
  201. // now have we loaded the structure..
  202. if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
  203. return true;
  204. }
  205. // - if not try building it..
  206. if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) {
  207. class_exists('DB_DataObject_Generator') ? '' :
  208. require_once 'DB/DataObject/Generator.php';
  209. $x = new DB_DataObject_Generator;
  210. $x->fillTableSchema($this->_database, $this->tableName());
  211. // should this fail!!!???
  212. return true;
  213. }
  214. $this->debug(
  215. "Can't find database schema: {$this->_database}/{$this->tableName()}\n"
  216. . 'in links file data: '
  217. . print_r($_DB_DATAOBJECT['INI'], true),
  218. 'databaseStructure',
  219. 5
  220. );
  221. // we have to die here!! - it causes chaos if we don't (including looping forever!)
  222. // Low level exception. No need for i18n as discussed with Brion.
  223. $this->raiseError(
  224. 'Unable to load schema for database and table '
  225. . '(turn debugging up to 5 for full error message)',
  226. DB_DATAOBJECT_ERROR_INVALIDARGS,
  227. PEAR_ERROR_DIE
  228. );
  229. return false;
  230. }
  231. /** For parseIniFiles */
  232. protected static $iniCache = array();
  233. /**
  234. * When switching site configurations, DB_DataObject was loading its
  235. * .ini files over and over, leaking gobs of memory.
  236. * This refactored helper function uses a local cache of .ini files
  237. * to minimize the leaks.
  238. *
  239. * @param array of .ini file names $schemas
  240. * @return array
  241. */
  242. protected function parseIniFiles(array $schemas)
  243. {
  244. $key = implode("|", $schemas);
  245. if (!isset(Safe_DataObject::$iniCache[$key])) {
  246. $data = array();
  247. foreach ($schemas as $ini) {
  248. if (file_exists($ini) && is_file($ini)) {
  249. $data = array_merge($data, parse_ini_file($ini, true));
  250. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  251. if (!is_readable($ini)) {
  252. $this->debug(
  253. "ini file is not readable: {$ini}",
  254. 'databaseStructure',
  255. 1
  256. );
  257. } else {
  258. $this->debug(
  259. "Loaded ini file: {$ini}",
  260. 'databaseStructure',
  261. 1
  262. );
  263. }
  264. }
  265. } else {
  266. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  267. $this->debug(
  268. "Missing ini file: {$ini}",
  269. 'databaseStructure',
  270. 1
  271. );
  272. }
  273. }
  274. }
  275. Safe_DataObject::$iniCache[$key] = $data;
  276. }
  277. return Safe_DataObject::$iniCache[$key];
  278. }
  279. }