storage.php 14 KB

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