]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/storage.php
locking table specific for better databases
[SourceForge/phpwiki.git] / lib / pear / DB / storage.php
1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
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 // +----------------------------------------------------------------------+
18 //
19 // $Id: storage.php,v 1.2 2004-04-26 20:44:37 rurban Exp $
20 //
21 // DB_storage: a class that lets you return SQL data as objects that
22 // can be manipulated and that updates the database accordingly.
23 //
24 // Based on DB 1.3 from the pear.php.net repository. 
25 // The only modifications made have been modification of the include paths. 
26 //
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');
29
30 require_once "PEAR.php";
31 require_once "DB.php";
32
33 /**
34  * DB_storage provides an object interface to a table row.  It lets
35  * you add, delete and change rows without using SQL.
36  *
37  * @author Stig Bakken <stig@php.net>
38  *
39  * @package DB
40  */
41 class DB_storage extends PEAR
42 {
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
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;
51
52     /** DB connection handle used for all transactions */
53     var $_dbh = null;
54
55     /** an assoc with the names of database fields stored as properties
56         in this object */
57     var $_properties = array();
58
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();
62
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;
67
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;
72
73     /**
74      * Constructor
75      *
76      * @param $table string the name of the database table
77      *
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
80      *
81      * @param $dbh object database connection object
82      *
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
87      *
88      */
89     function DB_storage($table, $keycolumn, &$dbh, $validator = null)
90     {
91         $this->PEAR('DB_Error');
92         $this->_table = $table;
93         $this->_keycolumn = $keycolumn;
94         $this->_dbh = $dbh;
95         $this->_readonly = false;
96         $this->_validator = $validator;
97     }
98
99     /**
100      * Utility method to build a "WHERE" clause to locate ourselves in
101      * the table.
102      *
103      * XXX future improvement: use rowids?
104      *
105      * @access private
106      */
107     function _makeWhere($keyval = null)
108     {
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]};
113                 }
114             }
115             $whereclause = '';
116             for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
117                 if ($i > 0) {
118                     $whereclause .= ' AND ';
119                 }
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';
125                 } else {
126                     $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
127                 }
128             }
129         } else {
130             if ($keyval === null) {
131                 $keyval = @$this->{$this->_keycolumn};
132             }
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';
138             } else {
139                 $whereclause .= ' = ' . $this->_dbh->quote($keyval);
140             }
141         }
142         return $whereclause;
143     }
144
145     /**
146      * Method used to initialize a DB_storage object from the
147      * configured table.
148      *
149      * @param $keyval mixed the key[s] of the row to fetch (string or array)
150      *
151      * @return int DB_OK on success, a DB error if not
152      */
153     function setup($keyval)
154     {
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)) {
160             return $sth;
161         }
162         $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
163         if (DB::isError($row)) {
164             return $row;
165         }
166         if (empty($row)) {
167             return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
168                                      $query, null, true);
169         }
170         foreach ($row as $key => $value) {
171             $this->_properties[$key] = true;
172             $this->$key = $value;
173         }
174         return DB_OK;
175     }
176
177     /**
178      * Create a new (empty) row in the configured table for this
179      * object.
180      */
181     function insert($newpk)
182     {
183         if (is_array($this->_keycolumn)) {
184             $primarykey = $this->_keycolumn;
185         } else {
186             $primarykey = array($this->_keycolumn);
187         }
188         settype($newpk, "array");
189         for ($i = 0; $i < sizeof($primarykey); $i++) {
190             $pkvals[] = $this->_dbh->quote($newpk[$i]);
191         }
192
193         $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
194                                   implode(",", $primarykey) . ") VALUES(" .
195                                   implode(",", $pkvals) . ")");
196         if (DB::isError($sth)) {
197             return $sth;
198         }
199         if (sizeof($newpk) == 1) {
200             $newpk = $newpk[0];
201         }
202         $this->setup($newpk);
203     }
204
205     /**
206      * Output a simple description of this DB_storage object.
207      * @return string object description
208      */
209     function toString()
210     {
211         $info = get_class($this);
212         $info .= " (table=";
213         $info .= $this->_table;
214         $info .= ", keycolumn=";
215         if (is_array($this->_keycolumn)) {
216             $info .= "(" . implode(",", $this->_keycolumn) . ")";
217         } else {
218             $info .= $this->_keycolumn;
219         }
220         $info .= ", dbh=";
221         if (is_object($this->_dbh)) {
222             $info .= $this->_dbh->toString();
223         } else {
224             $info .= "null";
225         }
226         $info .= ")";
227         if (sizeof($this->_properties)) {
228             $info .= " [loaded, key=";
229             $keyname = $this->_keycolumn;
230             if (is_array($keyname)) {
231                 $info .= "(";
232                 for ($i = 0; $i < sizeof($keyname); $i++) {
233                     if ($i > 0) {
234                         $info .= ",";
235                     }
236                     $info .= $this->$keyname[$i];
237                 }
238                 $info .= ")";
239             } else {
240                 $info .= $this->$keyname;
241             }
242             $info .= "]";
243         }
244         if (sizeof($this->_changes)) {
245             $info .= " [modified]";
246         }
247         return $info;
248     }
249
250     /**
251      * Dump the contents of this object to "standard output".
252      */
253     function dump()
254     {
255         reset($this->_properties);
256         while (list($prop, $foo) = each($this->_properties)) {
257             print "$prop = ";
258             print htmlentities($this->$prop);
259             print "<BR>\n";
260         }
261     }
262
263     /**
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
268      */
269     function &create($table, &$data)
270     {
271         $classname = get_class($this);
272         $obj = new $classname($table);
273         reset($data);
274         while (list($name, $value) = each($data)) {
275             $obj->_properties[$name] = true;
276             $obj->$name = &$value;
277         }
278         return $obj;
279     }
280
281     /**
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.
285      *
286      * @param $query SQL query
287      *
288      * @param $params parameter list in case you want to use
289      * prepare/execute mode
290      *
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.
295      */
296 // XXX commented out for now
297 /*
298     function loadFromQuery($query, $params = null)
299     {
300         if (sizeof($this->_properties)) {
301             if (sizeof($this->_changes)) {
302                 $this->store();
303                 $this->_changes = array();
304             }
305             $this->_properties = array();
306         }
307         $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
308         if (DB::isError($rowdata)) {
309             return $rowdata;
310         }
311         reset($rowdata);
312         $found_keycolumn = false;
313         while (list($key, $value) = each($rowdata)) {
314             if ($key == $this->_keycolumn) {
315                 $found_keycolumn = true;
316             }
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
321         }
322         if (!$found_keycolumn) {
323             $this->_readonly = true;
324             return DB_WARNING_READ_ONLY;
325         }
326         return DB_OK;
327     }
328 */
329
330     /**
331      * Modify an attriute value.
332      */
333     function set($property, $newvalue)
334     {
335         // only change if $property is known and object is not
336         // read-only
337         if ($this->_readonly) {
338             return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
339                                      null, null, null, true);
340         }
341         if (@isset($this->_properties[$property])) {
342             if (empty($this->_validator)) {
343                 $valid = true;
344             } else {
345                 $valid = @call_user_func($this->_validator,
346                                          $this->_table,
347                                          $property,
348                                          $newvalue,
349                                          $this->$property,
350                                          $this);
351             }
352             if ($valid) {
353                 $this->$property = $newvalue;
354                 @$this->_changes[$property]++;
355             } else {
356                 return $this->raiseError(null, DB_ERROR_INVALID, null,
357                                          null, "invalid field: $property",
358                                          null, true);
359             }
360             return true;
361         }
362         return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
363                                  null, "unknown field: $property",
364                                  null, true);
365     }
366
367     /**
368      * Fetch an attribute value.
369      *
370      * @param string attribute name
371      *
372      * @return attribute contents, or null if the attribute name is
373      * unknown
374      */
375     function &get($property)
376     {
377         // only return if $property is known
378         if (isset($this->_properties[$property])) {
379             return $this->$property;
380         }
381         return null;
382     }
383
384     /**
385      * Destructor, calls DB_storage::store() if there are changes
386      * that are to be kept.
387      */
388     function _DB_storage()
389     {
390         if (empty($this->_discard) && sizeof($this->_changes)) {
391             $this->store();
392         }
393         $this->_properties = array();
394         $this->_changes = array();
395         $this->_table = null;
396     }
397
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         while (list($name, $changed) = each($this->_changes)) {
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     /**
424      * Remove the row represented by this object from the database.
425      *
426      * @return mixed DB_OK or a DB error
427      */
428     function remove()
429     {
430         if ($this->_readonly) {
431             return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
432                                      null, null, null, true);
433         }
434         $query = 'DELETE FROM ' . $this->_table .' WHERE '.
435             $this->_makeWhere();
436         $res = $this->_dbh->query($query);
437         if (DB::isError($res)) {
438             return $res;
439         }
440         foreach ($this->_properties as $prop => $foo) {
441             unset($this->$prop);
442         }
443         $this->_properties = array();
444         $this->_changes = array();
445         return DB_OK;
446     }
447 }
448
449 ?>