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.28 2002/02/28 08:27:11 sebastian 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 |
|
25 |
require_once "PEAR.php"; |
26 |
require_once "DB.php"; |
27 |
|
28 |
/** |
29 |
* DB_storage provides an object interface to a table row. It lets |
30 |
* you add, delete and change rows without using SQL. |
31 |
* |
32 |
* @author Stig Bakken <stig@php.net> |
33 |
* |
34 |
* @package DB |
35 |
*/ |
36 |
class DB_storage extends PEAR |
37 |
{ |
38 |
/** the name of the table (or view, if the backend database supports |
39 |
updates in views) we hold data from */ |
40 |
var $_table = null; |
41 |
|
42 |
/** which column(s) in the table contains primary keys, can be a |
43 |
string for single-column primary keys, or an array of strings |
44 |
for multiple-column primary keys */ |
45 |
var $_keycolumn = null; |
46 |
|
47 |
/** DB connection handle used for all transactions */ |
48 |
var $_dbh = null; |
49 |
|
50 |
/** an assoc with the names of database fields stored as properties |
51 |
in this object */ |
52 |
var $_properties = array(); |
53 |
|
54 |
/** an assoc with the names of the properties in this object that |
55 |
have been changed since they were fetched from the database */ |
56 |
var $_changes = array(); |
57 |
|
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 |
|
63 |
/** function or method that implements a validator for fields that |
64 |
are set, this validator function returns true if the field is |
65 |
valid, false if not */ |
66 |
var $_validator = null; |
67 |
|
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 |
/** |
95 |
* Utility method to build a "WHERE" clause to locate ourselves in |
96 |
* the table. |
97 |
* |
98 |
* XXX future improvement: use rowids? |
99 |
* |
100 |
* @access private |
101 |
*/ |
102 |
function _makeWhere($keyval = null) |
103 |
{ |
104 |
if (is_array($this->_keycolumn)) { |
105 |
if ($keyval === null) { |
106 |
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { |
107 |
$keyval[] = $this->{$this->_keycolumn[$i]}; |
108 |
} |
109 |
} |
110 |
$whereclause = ''; |
111 |
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { |
112 |
if ($i > 0) { |
113 |
$whereclause .= ' AND '; |
114 |
} |
115 |
$whereclause .= $this->_keycolumn[$i]; |
116 |
if (is_null($keyval[$i])) { |
117 |
// there's not much point in having a NULL key, |
118 |
// but we support it anyway |
119 |
$whereclause .= ' IS NULL'; |
120 |
} else { |
121 |
$whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); |
122 |
} |
123 |
} |
124 |
} else { |
125 |
if ($keyval === null) { |
126 |
$keyval = @$this->{$this->_keycolumn}; |
127 |
} |
128 |
$whereclause = $this->_keycolumn; |
129 |
if (is_null($keyval)) { |
130 |
// there's not much point in having a NULL key, |
131 |
// but we support it anyway |
132 |
$whereclause .= ' IS NULL'; |
133 |
} else { |
134 |
$whereclause .= ' = ' . $this->_dbh->quote($keyval); |
135 |
} |
136 |
} |
137 |
return $whereclause; |
138 |
} |
139 |
|
140 |
/** |
141 |
* Method used to initialize a DB_storage object from the |
142 |
* configured table. |
143 |
* |
144 |
* @param $keyval mixed the key[s] of the row to fetch (string or array) |
145 |
* |
146 |
* @return int DB_OK on success, a DB error if not |
147 |
*/ |
148 |
function setup($keyval) |
149 |
{ |
150 |
$qval = $this->_dbh->quote($keyval); |
151 |
$whereclause = $this->_makeWhere($keyval); |
152 |
$query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; |
153 |
$sth = $this->_dbh->query($query); |
154 |
if (DB::isError($sth)) { |
155 |
return $sth; |
156 |
} |
157 |
$row = $sth->fetchRow(DB_FETCHMODE_ASSOC); |
158 |
if (DB::isError($row)) { |
159 |
return $row; |
160 |
} |
161 |
if (empty($row)) { |
162 |
return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, |
163 |
$query, null, true); |
164 |
} |
165 |
foreach ($row as $key => $value) { |
166 |
$this->_properties[$key] = true; |
167 |
$this->$key = $value; |
168 |
} |
169 |
return DB_OK; |
170 |
} |
171 |
|
172 |
/** |
173 |
* Create a new (empty) row in the configured table for this |
174 |
* object. |
175 |
*/ |
176 |
function insert($newpk) |
177 |
{ |
178 |
if (is_array($this->_keycolumn)) { |
179 |
$primarykey = $this->_keycolumn; |
180 |
} else { |
181 |
$primarykey = array($this->_keycolumn); |
182 |
} |
183 |
settype($newpk, "array"); |
184 |
for ($i = 0; $i < sizeof($primarykey); $i++) { |
185 |
$pkvals[] = $this->_dbh->quote($newpk[$i]); |
186 |
} |
187 |
|
188 |
$sth = $this->_dbh->query("INSERT INTO $this->_table (" . |
189 |
implode(",", $primarykey) . ") VALUES(" . |
190 |
implode(",", $pkvals) . ")"); |
191 |
if (DB::isError($sth)) { |
192 |
return $sth; |
193 |
} |
194 |
if (sizeof($newpk) == 1) { |
195 |
$newpk = $newpk[0]; |
196 |
} |
197 |
$this->setup($newpk); |
198 |
} |
199 |
|
200 |
/** |
201 |
* Output a simple description of this DB_storage object. |
202 |
* @return string object description |
203 |
*/ |
204 |
function toString() |
205 |
{ |
206 |
$info = get_class($this); |
207 |
$info .= " (table="; |
208 |
$info .= $this->_table; |
209 |
$info .= ", keycolumn="; |
210 |
if (is_array($this->_keycolumn)) { |
211 |
$info .= "(" . implode(",", $this->_keycolumn) . ")"; |
212 |
} else { |
213 |
$info .= $this->_keycolumn; |
214 |
} |
215 |
$info .= ", dbh="; |
216 |
if (is_object($this->_dbh)) { |
217 |
$info .= $this->_dbh->toString(); |
218 |
} else { |
219 |
$info .= "null"; |
220 |
} |
221 |
$info .= ")"; |
222 |
if (sizeof($this->_properties)) { |
223 |
$info .= " [loaded, key="; |
224 |
$keyname = $this->_keycolumn; |
225 |
if (is_array($keyname)) { |
226 |
$info .= "("; |
227 |
for ($i = 0; $i < sizeof($keyname); $i++) { |
228 |
if ($i > 0) { |
229 |
$info .= ","; |
230 |
} |
231 |
$info .= $this->$keyname[$i]; |
232 |
} |
233 |
$info .= ")"; |
234 |
} else { |
235 |
$info .= $this->$keyname; |
236 |
} |
237 |
$info .= "]"; |
238 |
} |
239 |
if (sizeof($this->_changes)) { |
240 |
$info .= " [modified]"; |
241 |
} |
242 |
return $info; |
243 |
} |
244 |
|
245 |
/** |
246 |
* Dump the contents of this object to "standard output". |
247 |
*/ |
248 |
function dump() |
249 |
{ |
250 |
reset($this->_properties); |
251 |
while (list($prop, $foo) = each($this->_properties)) { |
252 |
print "$prop = "; |
253 |
print htmlentities($this->$prop); |
254 |
print "<BR>\n"; |
255 |
} |
256 |
} |
257 |
|
258 |
/** |
259 |
* Static method used to create new DB storage objects. |
260 |
* @param $data assoc. array where the keys are the names |
261 |
* of properties/columns |
262 |
* @return object a new instance of DB_storage or a subclass of it |
263 |
*/ |
264 |
function &create($table, &$data) |
265 |
{ |
266 |
$classname = get_class($this); |
267 |
$obj = new $classname($table); |
268 |
reset($data); |
269 |
while (list($name, $value) = each($data)) { |
270 |
$obj->_properties[$name] = true; |
271 |
$obj->$name = &$value; |
272 |
} |
273 |
return $obj; |
274 |
} |
275 |
|
276 |
/** |
277 |
* Loads data into this object from the given query. If this |
278 |
* object already contains table data, changes will be saved and |
279 |
* the object re-initialized first. |
280 |
* |
281 |
* @param $query SQL query |
282 |
* |
283 |
* @param $params parameter list in case you want to use |
284 |
* prepare/execute mode |
285 |
* |
286 |
* @return int DB_OK on success, DB_WARNING_READ_ONLY if the |
287 |
* returned object is read-only (because the object's specified |
288 |
* key column was not found among the columns returned by $query), |
289 |
* or another DB error code in case of errors. |
290 |
*/ |
291 |
// XXX commented out for now |
292 |
/* |
293 |
function loadFromQuery($query, $params = null) |
294 |
{ |
295 |
if (sizeof($this->_properties)) { |
296 |
if (sizeof($this->_changes)) { |
297 |
$this->store(); |
298 |
$this->_changes = array(); |
299 |
} |
300 |
$this->_properties = array(); |
301 |
} |
302 |
$rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); |
303 |
if (DB::isError($rowdata)) { |
304 |
return $rowdata; |
305 |
} |
306 |
reset($rowdata); |
307 |
$found_keycolumn = false; |
308 |
while (list($key, $value) = each($rowdata)) { |
309 |
if ($key == $this->_keycolumn) { |
310 |
$found_keycolumn = true; |
311 |
} |
312 |
$this->_properties[$key] = true; |
313 |
$this->$key = &$value; |
314 |
unset($value); // have to unset, or all properties will |
315 |
// refer to the same value |
316 |
} |
317 |
if (!$found_keycolumn) { |
318 |
$this->_readonly = true; |
319 |
return DB_WARNING_READ_ONLY; |
320 |
} |
321 |
return DB_OK; |
322 |
} |
323 |
*/ |
324 |
|
325 |
/** |
326 |
* Modify an attriute value. |
327 |
*/ |
328 |
function set($property, $newvalue) |
329 |
{ |
330 |
// only change if $property is known and object is not |
331 |
// read-only |
332 |
if ($this->_readonly) { |
333 |
return $this->raiseError(null, DB_WARNING_READ_ONLY, null, |
334 |
null, null, null, true); |
335 |
} |
336 |
if (@isset($this->_properties[$property])) { |
337 |
if (empty($this->_validator)) { |
338 |
$valid = true; |
339 |
} else { |
340 |
$valid = @call_user_func($this->_validator, |
341 |
$this->_table, |
342 |
$property, |
343 |
$newvalue, |
344 |
$this->$property, |
345 |
$this); |
346 |
} |
347 |
if ($valid) { |
348 |
$this->$property = $newvalue; |
349 |
@$this->_changes[$property]++; |
350 |
} else { |
351 |
return $this->raiseError(null, DB_ERROR_INVALID, null, |
352 |
null, "invalid field: $property", |
353 |
null, true); |
354 |
} |
355 |
return true; |
356 |
} |
357 |
return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, |
358 |
null, "unknown field: $property", |
359 |
null, true); |
360 |
} |
361 |
|
362 |
/** |
363 |
* Fetch an attribute value. |
364 |
* |
365 |
* @param string attribute name |
366 |
* |
367 |
* @return attribute contents, or null if the attribute name is |
368 |
* unknown |
369 |
*/ |
370 |
function &get($property) |
371 |
{ |
372 |
// only return if $property is known |
373 |
if (isset($this->_properties[$property])) { |
374 |
return $this->$property; |
375 |
} |
376 |
return null; |
377 |
} |
378 |
|
379 |
/** |
380 |
* Destructor, calls DB_storage::store() if there are changes |
381 |
* that are to be kept. |
382 |
*/ |
383 |
function _DB_storage() |
384 |
{ |
385 |
if (empty($this->_discard) && sizeof($this->_changes)) { |
386 |
$this->store(); |
387 |
} |
388 |
$this->_properties = array(); |
389 |
$this->_changes = array(); |
390 |
$this->_table = null; |
391 |
} |
392 |
|
393 |
/** |
394 |
* Stores changes to this object in the database. |
395 |
* |
396 |
* @return DB_OK or a DB error |
397 |
*/ |
398 |
function store() |
399 |
{ |
400 |
while (list($name, $changed) = each($this->_changes)) { |
401 |
$params[] = &$this->$name; |
402 |
$vars[] = $name . ' = ?'; |
403 |
} |
404 |
if ($vars) { |
405 |
$query = 'UPDATE ' . $this->_table . ' SET ' . |
406 |
implode(', ', $vars) . ' WHERE ' . |
407 |
$this->_makeWhere(); |
408 |
$stmt = $this->_dbh->prepare($query); |
409 |
$res = $this->_dbh->execute($stmt, $params); |
410 |
if (DB::isError($res)) { |
411 |
return $res; |
412 |
} |
413 |
$this->_changes = array(); |
414 |
} |
415 |
return DB_OK; |
416 |
} |
417 |
|
418 |
/** |
419 |
* Remove the row represented by this object from the database. |
420 |
* |
421 |
* @return mixed DB_OK or a DB error |
422 |
*/ |
423 |
function remove() |
424 |
{ |
425 |
if ($this->_readonly) { |
426 |
return $this->raiseError(null, DB_WARNING_READ_ONLY, null, |
427 |
null, null, null, true); |
428 |
} |
429 |
$query = 'DELETE FROM ' . $this->_table .' WHERE '. |
430 |
$this->_makeWhere(); |
431 |
$res = $this->_dbh->query($query); |
432 |
if (DB::isError($res)) { |
433 |
return $res; |
434 |
} |
435 |
foreach ($this->_properties as $prop => $foo) { |
436 |
unset($this->$prop); |
437 |
} |
438 |
$this->_properties = array(); |
439 |
$this->_changes = array(); |
440 |
return DB_OK; |
441 |
} |
442 |
} |
443 |
|
444 |
?> |