3 // +----------------------------------------------------------------------+
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2002 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 // +----------------------------------------------------------------------+
19 // $Id: storage.php,v 1.2 2004-04-26 20:44:37 rurban Exp $
21 // DB_storage: a class that lets you return SQL data as objects that
22 // can be manipulated and that updates the database accordingly.
24 // Based on DB 1.3 from the pear.php.net repository.
25 // The only modifications made have been modification of the include paths.
27 rcs_id('$Id: storage.php,v 1.2 2004-04-26 20:44:37 rurban Exp $');
28 rcs_id('From Pear CVS: Id: ldap.php,v 1.9 2002/02/11 12:59:37 mj Exp');
30 require_once "PEAR.php";
31 require_once "DB.php";
34 * DB_storage provides an object interface to a table row. It lets
35 * you add, delete and change rows without using SQL.
37 * @author Stig Bakken <stig@php.net>
41 class DB_storage extends PEAR
43 /** the name of the table (or view, if the backend database supports
44 updates in views) we hold data from */
47 /** which column(s) in the table contains primary keys, can be a
48 string for single-column primary keys, or an array of strings
49 for multiple-column primary keys */
50 var $_keycolumn = null;
52 /** DB connection handle used for all transactions */
55 /** an assoc with the names of database fields stored as properties
57 var $_properties = array();
59 /** an assoc with the names of the properties in this object that
60 have been changed since they were fetched from the database */
61 var $_changes = array();
63 /** flag that decides if data in this object can be changed.
64 objects that don't have their table's key column in their
65 property lists will be flagged as read-only. */
66 var $_readonly = false;
68 /** function or method that implements a validator for fields that
69 are set, this validator function returns true if the field is
70 valid, false if not */
71 var $_validator = null;
76 * @param $table string the name of the database table
78 * @param $keycolumn mixed string with name of key column, or array of
79 * strings if the table has a primary key of more than one column
81 * @param $dbh object database connection object
83 * @param $validator mixed function or method used to validate
84 * each new value, called with three parameters: the name of the
85 * field/column that is changing, a reference to the new value and
86 * a reference to this object
89 function DB_storage($table, $keycolumn, &$dbh, $validator = null)
91 $this->PEAR('DB_Error');
92 $this->_table = $table;
93 $this->_keycolumn = $keycolumn;
95 $this->_readonly = false;
96 $this->_validator = $validator;
100 * Utility method to build a "WHERE" clause to locate ourselves in
103 * XXX future improvement: use rowids?
107 function _makeWhere($keyval = null)
109 if (is_array($this->_keycolumn)) {
110 if ($keyval === null) {
111 for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
112 $keyval[] = $this->{$this->_keycolumn[$i]};
116 for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
118 $whereclause .= ' AND ';
120 $whereclause .= $this->_keycolumn[$i];
121 if (is_null($keyval[$i])) {
122 // there's not much point in having a NULL key,
123 // but we support it anyway
124 $whereclause .= ' IS NULL';
126 $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
130 if ($keyval === null) {
131 $keyval = @$this->{$this->_keycolumn};
133 $whereclause = $this->_keycolumn;
134 if (is_null($keyval)) {
135 // there's not much point in having a NULL key,
136 // but we support it anyway
137 $whereclause .= ' IS NULL';
139 $whereclause .= ' = ' . $this->_dbh->quote($keyval);
146 * Method used to initialize a DB_storage object from the
149 * @param $keyval mixed the key[s] of the row to fetch (string or array)
151 * @return int DB_OK on success, a DB error if not
153 function setup($keyval)
155 $qval = $this->_dbh->quote($keyval);
156 $whereclause = $this->_makeWhere($keyval);
157 $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
158 $sth = $this->_dbh->query($query);
159 if (DB::isError($sth)) {
162 $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
163 if (DB::isError($row)) {
167 return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
170 foreach ($row as $key => $value) {
171 $this->_properties[$key] = true;
172 $this->$key = $value;
178 * Create a new (empty) row in the configured table for this
181 function insert($newpk)
183 if (is_array($this->_keycolumn)) {
184 $primarykey = $this->_keycolumn;
186 $primarykey = array($this->_keycolumn);
188 settype($newpk, "array");
189 for ($i = 0; $i < sizeof($primarykey); $i++) {
190 $pkvals[] = $this->_dbh->quote($newpk[$i]);
193 $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
194 implode(",", $primarykey) . ") VALUES(" .
195 implode(",", $pkvals) . ")");
196 if (DB::isError($sth)) {
199 if (sizeof($newpk) == 1) {
202 $this->setup($newpk);
206 * Output a simple description of this DB_storage object.
207 * @return string object description
211 $info = get_class($this);
213 $info .= $this->_table;
214 $info .= ", keycolumn=";
215 if (is_array($this->_keycolumn)) {
216 $info .= "(" . implode(",", $this->_keycolumn) . ")";
218 $info .= $this->_keycolumn;
221 if (is_object($this->_dbh)) {
222 $info .= $this->_dbh->toString();
227 if (sizeof($this->_properties)) {
228 $info .= " [loaded, key=";
229 $keyname = $this->_keycolumn;
230 if (is_array($keyname)) {
232 for ($i = 0; $i < sizeof($keyname); $i++) {
236 $info .= $this->$keyname[$i];
240 $info .= $this->$keyname;
244 if (sizeof($this->_changes)) {
245 $info .= " [modified]";
251 * Dump the contents of this object to "standard output".
255 reset($this->_properties);
256 while (list($prop, $foo) = each($this->_properties)) {
258 print htmlentities($this->$prop);
264 * Static method used to create new DB storage objects.
265 * @param $data assoc. array where the keys are the names
266 * of properties/columns
267 * @return object a new instance of DB_storage or a subclass of it
269 function &create($table, &$data)
271 $classname = get_class($this);
272 $obj = new $classname($table);
274 while (list($name, $value) = each($data)) {
275 $obj->_properties[$name] = true;
276 $obj->$name = &$value;
282 * Loads data into this object from the given query. If this
283 * object already contains table data, changes will be saved and
284 * the object re-initialized first.
286 * @param $query SQL query
288 * @param $params parameter list in case you want to use
289 * prepare/execute mode
291 * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
292 * returned object is read-only (because the object's specified
293 * key column was not found among the columns returned by $query),
294 * or another DB error code in case of errors.
296 // XXX commented out for now
298 function loadFromQuery($query, $params = null)
300 if (sizeof($this->_properties)) {
301 if (sizeof($this->_changes)) {
303 $this->_changes = array();
305 $this->_properties = array();
307 $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
308 if (DB::isError($rowdata)) {
312 $found_keycolumn = false;
313 while (list($key, $value) = each($rowdata)) {
314 if ($key == $this->_keycolumn) {
315 $found_keycolumn = true;
317 $this->_properties[$key] = true;
318 $this->$key = &$value;
319 unset($value); // have to unset, or all properties will
320 // refer to the same value
322 if (!$found_keycolumn) {
323 $this->_readonly = true;
324 return DB_WARNING_READ_ONLY;
331 * Modify an attriute value.
333 function set($property, $newvalue)
335 // only change if $property is known and object is not
337 if ($this->_readonly) {
338 return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
339 null, null, null, true);
341 if (@isset($this->_properties[$property])) {
342 if (empty($this->_validator)) {
345 $valid = @call_user_func($this->_validator,
353 $this->$property = $newvalue;
354 @$this->_changes[$property]++;
356 return $this->raiseError(null, DB_ERROR_INVALID, null,
357 null, "invalid field: $property",
362 return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
363 null, "unknown field: $property",
368 * Fetch an attribute value.
370 * @param string attribute name
372 * @return attribute contents, or null if the attribute name is
375 function &get($property)
377 // only return if $property is known
378 if (isset($this->_properties[$property])) {
379 return $this->$property;
385 * Destructor, calls DB_storage::store() if there are changes
386 * that are to be kept.
388 function _DB_storage()
390 if (empty($this->_discard) && sizeof($this->_changes)) {
393 $this->_properties = array();
394 $this->_changes = array();
395 $this->_table = null;
399 * Stores changes to this object in the database.
401 * @return DB_OK or a DB error
405 while (list($name, $changed) = each($this->_changes)) {
406 $params[] = &$this->$name;
407 $vars[] = $name . ' = ?';
410 $query = 'UPDATE ' . $this->_table . ' SET ' .
411 implode(', ', $vars) . ' WHERE ' .
413 $stmt = $this->_dbh->prepare($query);
414 $res = $this->_dbh->execute($stmt, $params);
415 if (DB::isError($res)) {
418 $this->_changes = array();
424 * Remove the row represented by this object from the database.
426 * @return mixed DB_OK or a DB error
430 if ($this->_readonly) {
431 return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
432 null, null, null, true);
434 $query = 'DELETE FROM ' . $this->_table .' WHERE '.
436 $res = $this->_dbh->query($query);
437 if (DB::isError($res)) {
440 foreach ($this->_properties as $prop => $foo) {
443 $this->_properties = array();
444 $this->_changes = array();