2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 // +----------------------------------------------------------------------+
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 // +----------------------------------------------------------------------+
22 require_once 'DB.php';
25 * Provides an object interface to a table row.
27 * It lets you add, delete and change rows using objects rather than SQL
33 * @author Stig Bakken <stig@php.net>
35 class DB_storage extends PEAR
39 /** the name of the table (or view, if the backend database supports
40 updates in views) we hold data from */
43 /** which column(s) in the table contains primary keys, can be a
44 string for single-column primary keys, or an array of strings
45 for multiple-column primary keys */
46 var $_keycolumn = null;
48 /** DB connection handle used for all transactions */
51 /** an assoc with the names of database fields stored as properties
53 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();
59 /** flag that decides if data in this object can be changed.
60 objects that don't have their table's key column in their
61 property lists will be flagged as read-only. */
62 var $_readonly = false;
64 /** function or method that implements a validator for fields that
65 are set, this validator function returns true if the field is
66 valid, false if not */
67 var $_validator = null;
75 * @param $table string the name of the database table
77 * @param $keycolumn mixed string with name of key column, or array of
78 * strings if the table has a primary key of more than one column
80 * @param $dbh object database connection object
82 * @param $validator mixed function or method used to validate
83 * each new value, called with three parameters: the name of the
84 * field/column that is changing, a reference to the new value and
85 * a reference to this object
88 function DB_storage($table, $keycolumn, &$dbh, $validator = null)
90 $this->PEAR('DB_Error');
91 $this->_table = $table;
92 $this->_keycolumn = $keycolumn;
94 $this->_readonly = false;
95 $this->_validator = $validator;
102 * Utility method to build a "WHERE" clause to locate ourselves in
105 * XXX future improvement: use rowids?
109 function _makeWhere($keyval = null)
111 if (is_array($this->_keycolumn)) {
112 if ($keyval === null) {
113 for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
114 $keyval[] = $this->{$this->_keycolumn[$i]};
118 for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
120 $whereclause .= ' AND ';
122 $whereclause .= $this->_keycolumn[$i];
123 if (is_null($keyval[$i])) {
124 // there's not much point in having a NULL key,
125 // but we support it anyway
126 $whereclause .= ' IS NULL';
128 $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
132 if ($keyval === null) {
133 $keyval = @$this->{$this->_keycolumn};
135 $whereclause = $this->_keycolumn;
136 if (is_null($keyval)) {
137 // there's not much point in having a NULL key,
138 // but we support it anyway
139 $whereclause .= ' IS NULL';
141 $whereclause .= ' = ' . $this->_dbh->quote($keyval);
151 * Method used to initialize a DB_storage object from the
154 * @param $keyval mixed the key[s] of the row to fetch (string or array)
156 * @return int DB_OK on success, a DB error if not
158 function setup($keyval)
160 $whereclause = $this->_makeWhere($keyval);
161 $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
162 $sth = $this->_dbh->query($query);
163 if (DB::isError($sth)) {
166 $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
167 if (DB::isError($row)) {
171 return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
174 foreach ($row as $key => $value) {
175 $this->_properties[$key] = true;
176 $this->$key = $value;
185 * Create a new (empty) row in the configured table for this
188 function insert($newpk)
190 if (is_array($this->_keycolumn)) {
191 $primarykey = $this->_keycolumn;
193 $primarykey = array($this->_keycolumn);
195 settype($newpk, "array");
196 for ($i = 0; $i < sizeof($primarykey); $i++) {
197 $pkvals[] = $this->_dbh->quote($newpk[$i]);
200 $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
201 implode(",", $primarykey) . ") VALUES(" .
202 implode(",", $pkvals) . ")");
203 if (DB::isError($sth)) {
206 if (sizeof($newpk) == 1) {
209 $this->setup($newpk);
216 * Output a simple description of this DB_storage object.
217 * @return string object description
221 $info = strtolower(get_class($this));
223 $info .= $this->_table;
224 $info .= ", keycolumn=";
225 if (is_array($this->_keycolumn)) {
226 $info .= "(" . implode(",", $this->_keycolumn) . ")";
228 $info .= $this->_keycolumn;
231 if (is_object($this->_dbh)) {
232 $info .= $this->_dbh->toString();
237 if (sizeof($this->_properties)) {
238 $info .= " [loaded, key=";
239 $keyname = $this->_keycolumn;
240 if (is_array($keyname)) {
242 for ($i = 0; $i < sizeof($keyname); $i++) {
246 $info .= $this->$keyname[$i];
250 $info .= $this->$keyname;
254 if (sizeof($this->_changes)) {
255 $info .= " [modified]";
264 * Dump the contents of this object to "standard output".
268 foreach ($this->_properties as $prop => $foo) {
270 print htmlentities($this->$prop);
279 * Static method used to create new DB storage objects.
280 * @param $data assoc. array where the keys are the names
281 * of properties/columns
282 * @return object a new instance of DB_storage or a subclass of it
284 function &create($table, &$data)
286 $classname = strtolower(get_class($this));
287 $obj =& new $classname($table);
288 foreach ($data as $name => $value) {
289 $obj->_properties[$name] = true;
290 $obj->$name = &$value;
296 // {{{ loadFromQuery()
299 * Loads data into this object from the given query. If this
300 * object already contains table data, changes will be saved and
301 * the object re-initialized first.
303 * @param $query SQL query
305 * @param $params parameter list in case you want to use
306 * prepare/execute mode
308 * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
309 * returned object is read-only (because the object's specified
310 * key column was not found among the columns returned by $query),
311 * or another DB error code in case of errors.
313 // XXX commented out for now
315 function loadFromQuery($query, $params = null)
317 if (sizeof($this->_properties)) {
318 if (sizeof($this->_changes)) {
320 $this->_changes = array();
322 $this->_properties = array();
324 $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
325 if (DB::isError($rowdata)) {
329 $found_keycolumn = false;
330 while (list($key, $value) = each($rowdata)) {
331 if ($key == $this->_keycolumn) {
332 $found_keycolumn = true;
334 $this->_properties[$key] = true;
335 $this->$key = &$value;
336 unset($value); // have to unset, or all properties will
337 // refer to the same value
339 if (!$found_keycolumn) {
340 $this->_readonly = true;
341 return DB_WARNING_READ_ONLY;
351 * Modify an attriute value.
353 function set($property, $newvalue)
355 // only change if $property is known and object is not
357 if ($this->_readonly) {
358 return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
359 null, null, null, true);
361 if (@isset($this->_properties[$property])) {
362 if (empty($this->_validator)) {
365 $valid = @call_user_func($this->_validator,
373 $this->$property = $newvalue;
374 if (empty($this->_changes[$property])) {
375 $this->_changes[$property] = 0;
377 $this->_changes[$property]++;
380 return $this->raiseError(null, DB_ERROR_INVALID, null,
381 null, "invalid field: $property",
386 return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
387 null, "unknown field: $property",
395 * Fetch an attribute value.
397 * @param string attribute name
399 * @return attribute contents, or null if the attribute name is
402 function &get($property)
404 // only return if $property is known
405 if (isset($this->_properties[$property])) {
406 return $this->$property;
416 * Destructor, calls DB_storage::store() if there are changes
417 * that are to be kept.
419 function _DB_storage()
421 if (sizeof($this->_changes)) {
424 $this->_properties = array();
425 $this->_changes = array();
426 $this->_table = null;
433 * Stores changes to this object in the database.
435 * @return DB_OK or a DB error
439 foreach ($this->_changes as $name => $foo) {
440 $params[] = &$this->$name;
441 $vars[] = $name . ' = ?';
444 $query = 'UPDATE ' . $this->_table . ' SET ' .
445 implode(', ', $vars) . ' WHERE ' .
447 $stmt = $this->_dbh->prepare($query);
448 $res = $this->_dbh->execute($stmt, $params);
449 if (DB::isError($res)) {
452 $this->_changes = array();
461 * Remove the row represented by this object from the database.
463 * @return mixed DB_OK or a DB error
467 if ($this->_readonly) {
468 return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
469 null, null, null, true);
471 $query = 'DELETE FROM ' . $this->_table .' WHERE '.
473 $res = $this->_dbh->query($query);
474 if (DB::isError($res)) {
477 foreach ($this->_properties as $prop => $foo) {
480 $this->_properties = array();
481 $this->_changes = array();