storage.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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.9.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. public $_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. public $_keycolumn = null;
  50. /** DB connection handle used for all transactions */
  51. public $_dbh = null;
  52. /** an assoc with the names of database fields stored as properties
  53. * in this object */
  54. public $_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. public $_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. public $_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. public $_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. public function __construct($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. * Create a new (empty) row in the configured table for this
  97. * object.
  98. * @param $newpk
  99. * @return |null
  100. */
  101. public function insert($newpk)
  102. {
  103. if (is_array($this->_keycolumn)) {
  104. $primarykey = $this->_keycolumn;
  105. } else {
  106. $primarykey = array($this->_keycolumn);
  107. }
  108. settype($newpk, "array");
  109. for ($i = 0; $i < sizeof($primarykey); $i++) {
  110. $pkvals[] = $this->_dbh->quote($newpk[$i]);
  111. }
  112. $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
  113. implode(",", $primarykey) . ") VALUES(" .
  114. implode(",", $pkvals) . ")");
  115. if (DB::isError($sth)) {
  116. return $sth;
  117. }
  118. if (sizeof($newpk) == 1) {
  119. $newpk = $newpk[0];
  120. }
  121. $this->setup($newpk);
  122. return null;
  123. }
  124. // }}}
  125. // {{{ setup()
  126. /**
  127. * Method used to initialize a DB_storage object from the
  128. * configured table.
  129. *
  130. * @param $keyval mixed the key[s] of the row to fetch (string or array)
  131. *
  132. * @return int|object
  133. */
  134. public function setup($keyval)
  135. {
  136. $whereclause = $this->_makeWhere($keyval);
  137. $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
  138. $sth = $this->_dbh->query($query);
  139. if (DB::isError($sth)) {
  140. return $sth;
  141. }
  142. $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
  143. if (DB::isError($row)) {
  144. return $row;
  145. }
  146. if (!$row) {
  147. return $this->raiseError(
  148. null,
  149. DB_ERROR_NOT_FOUND,
  150. null,
  151. null,
  152. $query,
  153. null,
  154. true
  155. );
  156. }
  157. foreach ($row as $key => $value) {
  158. $this->_properties[$key] = true;
  159. $this->$key = $value;
  160. }
  161. return DB_OK;
  162. }
  163. // }}}
  164. // {{{ insert()
  165. /**
  166. * Utility method to build a "WHERE" clause to locate ourselves in
  167. * the table.
  168. *
  169. * XXX future improvement: use rowids?
  170. *
  171. * @access private
  172. * @param null $keyval
  173. * @return mixed|string|null
  174. */
  175. public function _makeWhere($keyval = null)
  176. {
  177. if (is_array($this->_keycolumn)) {
  178. if ($keyval === null) {
  179. for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  180. $keyval[] = $this->{$this->_keycolumn[$i]};
  181. }
  182. }
  183. $whereclause = '';
  184. for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  185. if ($i > 0) {
  186. $whereclause .= ' AND ';
  187. }
  188. $whereclause .= $this->_keycolumn[$i];
  189. if (is_null($keyval[$i])) {
  190. // there's not much point in having a NULL key,
  191. // but we support it anyway
  192. $whereclause .= ' IS NULL';
  193. } else {
  194. $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
  195. }
  196. }
  197. } else {
  198. if ($keyval === null) {
  199. $keyval = @$this->{$this->_keycolumn};
  200. }
  201. $whereclause = $this->_keycolumn;
  202. if (is_null($keyval)) {
  203. // there's not much point in having a NULL key,
  204. // but we support it anyway
  205. $whereclause .= ' IS NULL';
  206. } else {
  207. $whereclause .= ' = ' . $this->_dbh->quote($keyval);
  208. }
  209. }
  210. return $whereclause;
  211. }
  212. // }}}
  213. // {{{ toString()
  214. /**
  215. * Output a simple description of this DB_storage object.
  216. * @return string object description
  217. */
  218. public function toString()
  219. {
  220. $info = strtolower(get_class($this));
  221. $info .= " (table=";
  222. $info .= $this->_table;
  223. $info .= ", keycolumn=";
  224. if (is_array($this->_keycolumn)) {
  225. $info .= "(" . implode(",", $this->_keycolumn) . ")";
  226. } else {
  227. $info .= $this->_keycolumn;
  228. }
  229. $info .= ", dbh=";
  230. if (is_object($this->_dbh)) {
  231. $info .= $this->_dbh->toString();
  232. } else {
  233. $info .= "null";
  234. }
  235. $info .= ")";
  236. if (sizeof($this->_properties)) {
  237. $info .= " [loaded, key=";
  238. $keyname = $this->_keycolumn;
  239. if (is_array($keyname)) {
  240. $info .= "(";
  241. for ($i = 0; $i < sizeof($keyname); $i++) {
  242. if ($i > 0) {
  243. $info .= ",";
  244. }
  245. $info .= $this->$keyname[$i];
  246. }
  247. $info .= ")";
  248. } else {
  249. $info .= $this->$keyname;
  250. }
  251. $info .= "]";
  252. }
  253. if (sizeof($this->_changes)) {
  254. $info .= " [modified]";
  255. }
  256. return $info;
  257. }
  258. // }}}
  259. // {{{ dump()
  260. /**
  261. * Dump the contents of this object to "standard output".
  262. */
  263. public function dump()
  264. {
  265. foreach ($this->_properties as $prop => $foo) {
  266. print "$prop = ";
  267. print htmlentities($this->$prop);
  268. print "<br />\n";
  269. }
  270. }
  271. // }}}
  272. // {{{ &create()
  273. /**
  274. * Static method used to create new DB storage objects.
  275. * @param $table
  276. * @param $data assoc. array where the keys are the names
  277. * of properties/columns
  278. * @return object a new instance of DB_storage or a subclass of it
  279. */
  280. public function &create($table, &$data)
  281. {
  282. $classname = strtolower(get_class($this));
  283. $obj = new $classname($table);
  284. foreach ($data as $name => $value) {
  285. $obj->_properties[$name] = true;
  286. $obj->$name = &$value;
  287. }
  288. return $obj;
  289. }
  290. // }}}
  291. // {{{ loadFromQuery()
  292. /**
  293. * Loads data into this object from the given query. If this
  294. * object already contains table data, changes will be saved and
  295. * the object re-initialized first.
  296. *
  297. * @param $query SQL query
  298. *
  299. * @param $params parameter list in case you want to use
  300. * prepare/execute mode
  301. *
  302. * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
  303. * returned object is read-only (because the object's specified
  304. * key column was not found among the columns returned by $query),
  305. * or another DB error code in case of errors.
  306. */
  307. // XXX commented out for now
  308. /*
  309. function loadFromQuery($query, $params = null)
  310. {
  311. if (sizeof($this->_properties)) {
  312. if (sizeof($this->_changes)) {
  313. $this->store();
  314. $this->_changes = array();
  315. }
  316. $this->_properties = array();
  317. }
  318. $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
  319. if (DB::isError($rowdata)) {
  320. return $rowdata;
  321. }
  322. reset($rowdata);
  323. $found_keycolumn = false;
  324. while (list($key, $value) = each($rowdata)) {
  325. if ($key == $this->_keycolumn) {
  326. $found_keycolumn = true;
  327. }
  328. $this->_properties[$key] = true;
  329. $this->$key = &$value;
  330. unset($value); // have to unset, or all properties will
  331. // refer to the same value
  332. }
  333. if (!$found_keycolumn) {
  334. $this->_readonly = true;
  335. return DB_WARNING_READ_ONLY;
  336. }
  337. return DB_OK;
  338. }
  339. */
  340. // }}}
  341. // {{{ set()
  342. /**
  343. * Modify an attriute value.
  344. * @param $property
  345. * @param $newvalue
  346. * @return bool|object
  347. */
  348. public function set($property, $newvalue)
  349. {
  350. // only change if $property is known and object is not
  351. // read-only
  352. if ($this->_readonly) {
  353. return $this->raiseError(
  354. null,
  355. DB_WARNING_READ_ONLY,
  356. null,
  357. null,
  358. null,
  359. null,
  360. true
  361. );
  362. }
  363. if (@isset($this->_properties[$property])) {
  364. if (empty($this->_validator)) {
  365. $valid = true;
  366. } else {
  367. $valid = @call_user_func(
  368. $this->_validator,
  369. $this->_table,
  370. $property,
  371. $newvalue,
  372. $this->$property,
  373. $this
  374. );
  375. }
  376. if ($valid) {
  377. $this->$property = $newvalue;
  378. if (empty($this->_changes[$property])) {
  379. $this->_changes[$property] = 0;
  380. } else {
  381. $this->_changes[$property]++;
  382. }
  383. } else {
  384. return $this->raiseError(
  385. null,
  386. DB_ERROR_INVALID,
  387. null,
  388. null,
  389. "invalid field: $property",
  390. null,
  391. true
  392. );
  393. }
  394. return true;
  395. }
  396. return $this->raiseError(
  397. null,
  398. DB_ERROR_NOSUCHFIELD,
  399. null,
  400. null,
  401. "unknown field: $property",
  402. null,
  403. true
  404. );
  405. }
  406. // }}}
  407. // {{{ &get()
  408. /**
  409. * Fetch an attribute value.
  410. *
  411. * @param string attribute name
  412. *
  413. * @return attribute contents, or null if the attribute name is
  414. * unknown
  415. */
  416. public function &get($property)
  417. {
  418. // only return if $property is known
  419. if (isset($this->_properties[$property])) {
  420. return $this->$property;
  421. }
  422. $tmp = null;
  423. return $tmp;
  424. }
  425. // }}}
  426. // {{{ _DB_storage()
  427. /**
  428. * Destructor, calls DB_storage::store() if there are changes
  429. * that are to be kept.
  430. */
  431. public function _DB_storage()
  432. {
  433. if (sizeof($this->_changes)) {
  434. $this->store();
  435. }
  436. $this->_properties = array();
  437. $this->_changes = array();
  438. $this->_table = null;
  439. }
  440. // }}}
  441. // {{{ store()
  442. /**
  443. * Stores changes to this object in the database.
  444. *
  445. * @return DB_OK|int
  446. */
  447. public function store()
  448. {
  449. $params = array();
  450. $vars = array();
  451. foreach ($this->_changes as $name => $foo) {
  452. $params[] = &$this->$name;
  453. $vars[] = $name . ' = ?';
  454. }
  455. if ($vars) {
  456. $query = 'UPDATE ' . $this->_table . ' SET ' .
  457. implode(', ', $vars) . ' WHERE ' .
  458. $this->_makeWhere();
  459. $stmt = $this->_dbh->prepare($query);
  460. $res = $this->_dbh->execute($stmt, $params);
  461. if (DB::isError($res)) {
  462. return $res;
  463. }
  464. $this->_changes = array();
  465. }
  466. return DB_OK;
  467. }
  468. // }}}
  469. // {{{ remove()
  470. /**
  471. * Remove the row represented by this object from the database.
  472. *
  473. * @return mixed DB_OK or a DB error
  474. */
  475. public function remove()
  476. {
  477. if ($this->_readonly) {
  478. return $this->raiseError(
  479. null,
  480. DB_WARNING_READ_ONLY,
  481. null,
  482. null,
  483. null,
  484. null,
  485. true
  486. );
  487. }
  488. $query = 'DELETE FROM ' . $this->_table . ' WHERE ' .
  489. $this->_makeWhere();
  490. $res = $this->_dbh->query($query);
  491. if (DB::isError($res)) {
  492. return $res;
  493. }
  494. foreach ($this->_properties as $prop => $foo) {
  495. unset($this->$prop);
  496. }
  497. $this->_properties = array();
  498. $this->_changes = array();
  499. return DB_OK;
  500. }
  501. // }}}
  502. }
  503. /*
  504. * Local variables:
  505. * tab-width: 4
  506. * c-basic-offset: 4
  507. * End:
  508. */