storage.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Stig Bakken <stig@php.net> |
  17. // | Maintainer: Daniel Convissor <danielc@php.net> |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: storage.php,v 1.17 2004/02/04 01:42:05 danielc Exp $
  21. require_once 'DB.php';
  22. /**
  23. * Provides an object interface to a table row.
  24. *
  25. * It lets you add, delete and change rows using objects rather than SQL
  26. * statements.
  27. *
  28. * @package DB
  29. * @version $Id: storage.php,v 1.17 2004/02/04 01:42:05 danielc Exp $
  30. * @category Database
  31. * @author Stig Bakken <stig@php.net>
  32. */
  33. class DB_storage extends PEAR
  34. {
  35. // {{{ properties
  36. /** the name of the table (or view, if the backend database supports
  37. updates in views) we hold data from */
  38. var $_table = null;
  39. /** which column(s) in the table contains primary keys, can be a
  40. string for single-column primary keys, or an array of strings
  41. for multiple-column primary keys */
  42. var $_keycolumn = null;
  43. /** DB connection handle used for all transactions */
  44. var $_dbh = null;
  45. /** an assoc with the names of database fields stored as properties
  46. in this object */
  47. var $_properties = array();
  48. /** an assoc with the names of the properties in this object that
  49. have been changed since they were fetched from the database */
  50. var $_changes = array();
  51. /** flag that decides if data in this object can be changed.
  52. objects that don't have their table's key column in their
  53. property lists will be flagged as read-only. */
  54. var $_readonly = false;
  55. /** function or method that implements a validator for fields that
  56. are set, this validator function returns true if the field is
  57. valid, false if not */
  58. var $_validator = null;
  59. // }}}
  60. // {{{ constructor
  61. /**
  62. * Constructor
  63. *
  64. * @param $table string the name of the database table
  65. *
  66. * @param $keycolumn mixed string with name of key column, or array of
  67. * strings if the table has a primary key of more than one column
  68. *
  69. * @param $dbh object database connection object
  70. *
  71. * @param $validator mixed function or method used to validate
  72. * each new value, called with three parameters: the name of the
  73. * field/column that is changing, a reference to the new value and
  74. * a reference to this object
  75. *
  76. */
  77. function DB_storage($table, $keycolumn, &$dbh, $validator = null)
  78. {
  79. $this->PEAR('DB_Error');
  80. $this->_table = $table;
  81. $this->_keycolumn = $keycolumn;
  82. $this->_dbh = $dbh;
  83. $this->_readonly = false;
  84. $this->_validator = $validator;
  85. }
  86. // }}}
  87. // {{{ _makeWhere()
  88. /**
  89. * Utility method to build a "WHERE" clause to locate ourselves in
  90. * the table.
  91. *
  92. * XXX future improvement: use rowids?
  93. *
  94. * @access private
  95. */
  96. function _makeWhere($keyval = null)
  97. {
  98. if (is_array($this->_keycolumn)) {
  99. if ($keyval === null) {
  100. for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  101. $keyval[] = $this->{$this->_keycolumn[$i]};
  102. }
  103. }
  104. $whereclause = '';
  105. for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  106. if ($i > 0) {
  107. $whereclause .= ' AND ';
  108. }
  109. $whereclause .= $this->_keycolumn[$i];
  110. if (is_null($keyval[$i])) {
  111. // there's not much point in having a NULL key,
  112. // but we support it anyway
  113. $whereclause .= ' IS NULL';
  114. } else {
  115. $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
  116. }
  117. }
  118. } else {
  119. if ($keyval === null) {
  120. $keyval = @$this->{$this->_keycolumn};
  121. }
  122. $whereclause = $this->_keycolumn;
  123. if (is_null($keyval)) {
  124. // there's not much point in having a NULL key,
  125. // but we support it anyway
  126. $whereclause .= ' IS NULL';
  127. } else {
  128. $whereclause .= ' = ' . $this->_dbh->quote($keyval);
  129. }
  130. }
  131. return $whereclause;
  132. }
  133. // }}}
  134. // {{{ setup()
  135. /**
  136. * Method used to initialize a DB_storage object from the
  137. * configured table.
  138. *
  139. * @param $keyval mixed the key[s] of the row to fetch (string or array)
  140. *
  141. * @return int DB_OK on success, a DB error if not
  142. */
  143. function setup($keyval)
  144. {
  145. $whereclause = $this->_makeWhere($keyval);
  146. $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
  147. $sth = $this->_dbh->query($query);
  148. if (DB::isError($sth)) {
  149. return $sth;
  150. }
  151. $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
  152. if (DB::isError($row)) {
  153. return $row;
  154. }
  155. if (!$row) {
  156. return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
  157. $query, null, true);
  158. }
  159. foreach ($row as $key => $value) {
  160. $this->_properties[$key] = true;
  161. $this->$key = $value;
  162. }
  163. return DB_OK;
  164. }
  165. // }}}
  166. // {{{ insert()
  167. /**
  168. * Create a new (empty) row in the configured table for this
  169. * object.
  170. */
  171. function insert($newpk)
  172. {
  173. if (is_array($this->_keycolumn)) {
  174. $primarykey = $this->_keycolumn;
  175. } else {
  176. $primarykey = array($this->_keycolumn);
  177. }
  178. settype($newpk, "array");
  179. for ($i = 0; $i < sizeof($primarykey); $i++) {
  180. $pkvals[] = $this->_dbh->quote($newpk[$i]);
  181. }
  182. $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
  183. implode(",", $primarykey) . ") VALUES(" .
  184. implode(",", $pkvals) . ")");
  185. if (DB::isError($sth)) {
  186. return $sth;
  187. }
  188. if (sizeof($newpk) == 1) {
  189. $newpk = $newpk[0];
  190. }
  191. $this->setup($newpk);
  192. }
  193. // }}}
  194. // {{{ toString()
  195. /**
  196. * Output a simple description of this DB_storage object.
  197. * @return string object description
  198. */
  199. function toString()
  200. {
  201. $info = strtolower(get_class($this));
  202. $info .= " (table=";
  203. $info .= $this->_table;
  204. $info .= ", keycolumn=";
  205. if (is_array($this->_keycolumn)) {
  206. $info .= "(" . implode(",", $this->_keycolumn) . ")";
  207. } else {
  208. $info .= $this->_keycolumn;
  209. }
  210. $info .= ", dbh=";
  211. if (is_object($this->_dbh)) {
  212. $info .= $this->_dbh->toString();
  213. } else {
  214. $info .= "null";
  215. }
  216. $info .= ")";
  217. if (sizeof($this->_properties)) {
  218. $info .= " [loaded, key=";
  219. $keyname = $this->_keycolumn;
  220. if (is_array($keyname)) {
  221. $info .= "(";
  222. for ($i = 0; $i < sizeof($keyname); $i++) {
  223. if ($i > 0) {
  224. $info .= ",";
  225. }
  226. $info .= $this->$keyname[$i];
  227. }
  228. $info .= ")";
  229. } else {
  230. $info .= $this->$keyname;
  231. }
  232. $info .= "]";
  233. }
  234. if (sizeof($this->_changes)) {
  235. $info .= " [modified]";
  236. }
  237. return $info;
  238. }
  239. // }}}
  240. // {{{ dump()
  241. /**
  242. * Dump the contents of this object to "standard output".
  243. */
  244. function dump()
  245. {
  246. foreach ($this->_properties as $prop => $foo) {
  247. print "$prop = ";
  248. print htmlentities($this->$prop);
  249. print "<br />\n";
  250. }
  251. }
  252. // }}}
  253. // {{{ &create()
  254. /**
  255. * Static method used to create new DB storage objects.
  256. * @param $data assoc. array where the keys are the names
  257. * of properties/columns
  258. * @return object a new instance of DB_storage or a subclass of it
  259. */
  260. function &create($table, &$data)
  261. {
  262. $classname = strtolower(get_class($this));
  263. $obj =& new $classname($table);
  264. foreach ($data as $name => $value) {
  265. $obj->_properties[$name] = true;
  266. $obj->$name = &$value;
  267. }
  268. return $obj;
  269. }
  270. // }}}
  271. // {{{ loadFromQuery()
  272. /**
  273. * Loads data into this object from the given query. If this
  274. * object already contains table data, changes will be saved and
  275. * the object re-initialized first.
  276. *
  277. * @param $query SQL query
  278. *
  279. * @param $params parameter list in case you want to use
  280. * prepare/execute mode
  281. *
  282. * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
  283. * returned object is read-only (because the object's specified
  284. * key column was not found among the columns returned by $query),
  285. * or another DB error code in case of errors.
  286. */
  287. // XXX commented out for now
  288. /*
  289. function loadFromQuery($query, $params = null)
  290. {
  291. if (sizeof($this->_properties)) {
  292. if (sizeof($this->_changes)) {
  293. $this->store();
  294. $this->_changes = array();
  295. }
  296. $this->_properties = array();
  297. }
  298. $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
  299. if (DB::isError($rowdata)) {
  300. return $rowdata;
  301. }
  302. reset($rowdata);
  303. $found_keycolumn = false;
  304. while (list($key, $value) = each($rowdata)) {
  305. if ($key == $this->_keycolumn) {
  306. $found_keycolumn = true;
  307. }
  308. $this->_properties[$key] = true;
  309. $this->$key = &$value;
  310. unset($value); // have to unset, or all properties will
  311. // refer to the same value
  312. }
  313. if (!$found_keycolumn) {
  314. $this->_readonly = true;
  315. return DB_WARNING_READ_ONLY;
  316. }
  317. return DB_OK;
  318. }
  319. */
  320. // }}}
  321. // {{{ set()
  322. /**
  323. * Modify an attriute value.
  324. */
  325. function set($property, $newvalue)
  326. {
  327. // only change if $property is known and object is not
  328. // read-only
  329. if ($this->_readonly) {
  330. return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
  331. null, null, null, true);
  332. }
  333. if (@isset($this->_properties[$property])) {
  334. if (empty($this->_validator)) {
  335. $valid = true;
  336. } else {
  337. $valid = @call_user_func($this->_validator,
  338. $this->_table,
  339. $property,
  340. $newvalue,
  341. $this->$property,
  342. $this);
  343. }
  344. if ($valid) {
  345. $this->$property = $newvalue;
  346. if (empty($this->_changes[$property])) {
  347. $this->_changes[$property] = 0;
  348. } else {
  349. $this->_changes[$property]++;
  350. }
  351. } else {
  352. return $this->raiseError(null, DB_ERROR_INVALID, null,
  353. null, "invalid field: $property",
  354. null, true);
  355. }
  356. return true;
  357. }
  358. return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
  359. null, "unknown field: $property",
  360. null, true);
  361. }
  362. // }}}
  363. // {{{ &get()
  364. /**
  365. * Fetch an attribute value.
  366. *
  367. * @param string attribute name
  368. *
  369. * @return attribute contents, or null if the attribute name is
  370. * unknown
  371. */
  372. function &get($property)
  373. {
  374. // only return if $property is known
  375. if (isset($this->_properties[$property])) {
  376. return $this->$property;
  377. }
  378. $tmp = null;
  379. return $tmp;
  380. }
  381. // }}}
  382. // {{{ _DB_storage()
  383. /**
  384. * Destructor, calls DB_storage::store() if there are changes
  385. * that are to be kept.
  386. */
  387. function _DB_storage()
  388. {
  389. if (sizeof($this->_changes)) {
  390. $this->store();
  391. }
  392. $this->_properties = array();
  393. $this->_changes = array();
  394. $this->_table = null;
  395. }
  396. // }}}
  397. // {{{ store()
  398. /**
  399. * Stores changes to this object in the database.
  400. *
  401. * @return DB_OK or a DB error
  402. */
  403. function store()
  404. {
  405. foreach ($this->_changes as $name => $foo) {
  406. $params[] = &$this->$name;
  407. $vars[] = $name . ' = ?';
  408. }
  409. if ($vars) {
  410. $query = 'UPDATE ' . $this->_table . ' SET ' .
  411. implode(', ', $vars) . ' WHERE ' .
  412. $this->_makeWhere();
  413. $stmt = $this->_dbh->prepare($query);
  414. $res = $this->_dbh->execute($stmt, $params);
  415. if (DB::isError($res)) {
  416. return $res;
  417. }
  418. $this->_changes = array();
  419. }
  420. return DB_OK;
  421. }
  422. // }}}
  423. // {{{ remove()
  424. /**
  425. * Remove the row represented by this object from the database.
  426. *
  427. * @return mixed DB_OK or a DB error
  428. */
  429. function remove()
  430. {
  431. if ($this->_readonly) {
  432. return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
  433. null, null, null, true);
  434. }
  435. $query = 'DELETE FROM ' . $this->_table .' WHERE '.
  436. $this->_makeWhere();
  437. $res = $this->_dbh->query($query);
  438. if (DB::isError($res)) {
  439. return $res;
  440. }
  441. foreach ($this->_properties as $prop => $foo) {
  442. unset($this->$prop);
  443. }
  444. $this->_properties = array();
  445. $this->_changes = array();
  446. return DB_OK;
  447. }
  448. // }}}
  449. }
  450. /*
  451. * Local variables:
  452. * tab-width: 4
  453. * c-basic-offset: 4
  454. * End:
  455. */
  456. ?>