Commit | Line | Data |
---|---|---|
26d00fe5 FB |
1 | <?php |
2 | /*************************************************************************** | |
e92ecb8c | 3 | * Copyright (C) 2003-2011 Polytechnique.org * |
26d00fe5 FB |
4 | * http://opensource.polytechnique.org/ * |
5 | * * | |
6 | * This program is free software; you can redistribute it and/or modify * | |
7 | * it under the terms of the GNU General Public License as published by * | |
8 | * the Free Software Foundation; either version 2 of the License, or * | |
9 | * (at your option) any later version. * | |
10 | * * | |
11 | * This program is distributed in the hope that it will be useful, * | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
14 | * GNU General Public License for more details. * | |
15 | * * | |
16 | * You should have received a copy of the GNU General Public License * | |
17 | * along with this program; if not, write to the Free Software * | |
18 | * Foundation, Inc., * | |
19 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * | |
20 | ***************************************************************************/ | |
21 | ||
22 | class PlDBBadValueException extends PlException | |
23 | { | |
24 | public function __construct($value, PlDBTableField $field, $reason) | |
25 | { | |
26 | parent::__construct('Erreur lors de l\'accès à la base de données', | |
27 | 'Illegal value '. (is_null($value) ? '(null)' : '(\'' . $value . '\')') | |
28 | . ' for field (\'' . $field->table->table . '.' . $field->name . '\'): ' | |
29 | . $reason); | |
30 | } | |
31 | } | |
32 | ||
33 | class PlDBNoSuchFieldException extends PlException | |
34 | { | |
35 | public function __construct($field, PlDBTable $table) | |
36 | { | |
37 | parent::__construct('Erreur lors de l\'accès à la base de données', | |
38 | 'No such field ' . $field . ' in table ' . $table->table); | |
39 | } | |
40 | } | |
41 | ||
71db9fda FB |
42 | class PlDBNoSuchKeyException extends PlException |
43 | { | |
44 | public function __construct($key, PlDBTable $table) | |
45 | { | |
46 | parent::__construct('Erreur lors de l\'accès à la base de données', | |
47 | 'No such key ' . $key . ' in table ' . $table->table); | |
48 | } | |
49 | } | |
50 | ||
51 | ||
26d00fe5 FB |
52 | class PlDBIncompleteEntryDescription extends PlException |
53 | { | |
54 | public function __construct($field, PlDBTable $table) | |
55 | { | |
56 | parent::__construct('Erreur lors de l\'accès à la base de données', | |
57 | 'The field ' . $field . ' is required to describe an entry in table ' | |
58 | . $table->table); | |
59 | } | |
60 | } | |
61 | ||
62 | class PlDBTableField | |
63 | { | |
64 | public $table; | |
65 | ||
66 | public $name; | |
67 | public $inPrimaryKey; | |
26d00fe5 FB |
68 | |
69 | public $type; | |
70 | public $typeLength; | |
71 | public $typeParameters; | |
72 | ||
73 | public $allowNull; | |
74 | public $defaultValue; | |
75 | public $autoIncrement; | |
76 | ||
22b7e8d8 | 77 | private $validator; |
701cf973 FB |
78 | private $formatter; |
79 | ||
26d00fe5 FB |
80 | public function __construct(array $column) |
81 | { | |
82 | $this->name = $column['Field']; | |
83 | $this->typeParameters = explode(' ', str_replace(array('(', ')', ',', '\''), ' ', | |
84 | $column['Type'])); | |
85 | $this->type = array_shift($this->typeParameters); | |
86 | if ($this->type == 'enum' || $this->type == 'set') { | |
87 | $this->typeParameters = new PlFlagSet(implode(',', $this->typeParameters)); | |
88 | } else if (ctype_digit($this->typeParameters[0])) { | |
89 | $this->typeLength = intval($this->typeParameters[0]); | |
90 | array_shift($this->typeParameters); | |
91 | } | |
92 | $this->allowNull = ($column['Null'] === 'YES'); | |
93 | $this->autoIncrement = (strpos($column['Extra'], 'auto_increment') !== false); | |
71db9fda | 94 | $this->inPrimaryKey = ($column['Key'] == 'PRI'); |
26d00fe5 FB |
95 | |
96 | try { | |
97 | $this->defaultValue = $this->format($column['Default']); | |
98 | } catch (PlDBBadValueException $e) { | |
99 | $this->defaultValue = null; | |
100 | } | |
101 | } | |
102 | ||
701cf973 FB |
103 | public function registerFormatter($class) |
104 | { | |
105 | $this->formatter = $class; | |
106 | } | |
107 | ||
22b7e8d8 FB |
108 | public function registerValidator($class) |
109 | { | |
110 | $this->validator = $class; | |
111 | } | |
112 | ||
26d00fe5 FB |
113 | public function format($value, $badNullFallbackToDefault = false) |
114 | { | |
115 | if (is_null($value)) { | |
116 | if ($this->allowNull || $this->autoIncrement) { | |
117 | return $value; | |
118 | } | |
119 | if ($badNullFallbackToDefault) { | |
120 | return $this->defaultValue; | |
121 | } | |
122 | throw new PlDBBadValueException($value, $this, 'null not allowed'); | |
22b7e8d8 FB |
123 | } |
124 | if (!is_null($this->validator)) { | |
125 | $class = $this->validator; | |
126 | new $class($this, $value); | |
127 | } | |
128 | if (!is_null($this->formatter)) { | |
701cf973 FB |
129 | $class = $this->formatter; |
130 | $value = new $class($this, $value); | |
26d00fe5 FB |
131 | } else if ($this->type == 'enum') { |
132 | if (!$this->typeParameters->hasFlag($value)) { | |
133 | throw new PlDBBadValueException($value, $this, 'invalid value for enum ' . $this->typeParameters->flags()); | |
134 | } | |
135 | return $value; | |
136 | } else if ($this->type == 'set') { | |
137 | $value = new PlFlagSet($value); | |
138 | foreach ($value as $flag) { | |
139 | if (!$this->typeParameters->hasFlag($flag)) { | |
140 | throw new PlDBBadValueException($value, $this, 'invalid flag for set ' . $this->typeParameters->flags()); | |
141 | } | |
142 | } | |
143 | return $value; | |
144 | } else if (ends_with($this->type, 'int')) { | |
145 | if (!is_int($value) && !ctype_digit($value)) { | |
146 | throw new PlDBBadValueException($value, $this, 'value is not an integer'); | |
147 | } | |
148 | $value = intval($value); | |
149 | if (count($this->typeParameters) > 0 && $this->typeParameters[0] == 'unsigned') { | |
150 | if ($value < 0) { | |
151 | throw new PlDBBadValueException($value, $this, 'value is negative in an unsigned field'); | |
152 | } | |
153 | } | |
154 | /* TODO: Check bounds */ | |
155 | return $value; | |
701cf973 | 156 | } else if (ends_with($this->type, 'char')) { |
26d00fe5 FB |
157 | if (strlen($value) > $this->typeLength) { |
158 | throw new PlDBBadValueException($value, $this, 'value is expected to be at most ' . $this->typeLength . ' characters long, ' . strlen($value) . ' given'); | |
159 | } | |
160 | return $value; | |
20030248 | 161 | } else if (starts_with($this->type, 'date') || $this->type == 'timestamp') { |
701cf973 | 162 | return new DateFieldFormatter($this, $value); |
26d00fe5 | 163 | } |
26d00fe5 FB |
164 | return $value; |
165 | } | |
166 | } | |
167 | ||
22b7e8d8 | 168 | interface PlDBTableFieldValidator |
701cf973 FB |
169 | { |
170 | public function __construct(PlDBTableField $field, $value); | |
171 | } | |
172 | ||
c504af53 | 173 | interface PlDBTableFieldFormatter extends PlDBTableFieldValidator, XDBFormat, PlExportable |
22b7e8d8 FB |
174 | { |
175 | } | |
176 | ||
701cf973 | 177 | class DateFieldFormatter implements PlDBTableFieldFormatter |
20030248 FB |
178 | { |
179 | private $datetime; | |
180 | private $storageFormat; | |
181 | ||
701cf973 | 182 | public function __construct(PlDBTableField $field, $date) |
20030248 | 183 | { |
90e41060 | 184 | $this->datetime = make_datetime($date); |
701cf973 FB |
185 | if (is_null($this->datetime)) { |
186 | throw new PlDBBadValueException($date, $field, 'value is expected to be a date/time, ' . $date . ' given'); | |
187 | } | |
188 | if ($field->type == 'date') { | |
189 | $this->storageFormat = 'Y-m-d'; | |
190 | } else if ($field->type == 'datetime') { | |
191 | $this->storageFormat = 'Y-m-d H:i:s'; | |
192 | } else { | |
193 | $this->storageFormat = 'U'; | |
194 | } | |
20030248 FB |
195 | } |
196 | ||
197 | public function format() | |
198 | { | |
c504af53 | 199 | return XDB::escape($this->export()); |
20030248 FB |
200 | } |
201 | ||
202 | public function date($format) | |
203 | { | |
204 | return $this->datetime->format($format); | |
205 | } | |
c504af53 FB |
206 | |
207 | public function export() | |
208 | { | |
209 | return $this->datetime->format($this->storageFormat); | |
210 | } | |
20030248 FB |
211 | } |
212 | ||
701cf973 FB |
213 | class JSonFieldFormatter implements PlDBTableFieldFormatter, ArrayAccess |
214 | { | |
215 | private $data; | |
216 | ||
217 | public function __construct(PlDBTableField $field, $data) | |
218 | { | |
219 | if (strpos($field->type, 'text') === false) { | |
220 | throw new PlDBBadValueException($data, $field, 'json formatting requires a text field'); | |
221 | } | |
222 | ||
223 | if (is_string($data)) { | |
224 | $this->data = json_decode($data, true); | |
225 | } else if (is_object($data)) { | |
3905332e FB |
226 | if ($data instanceof PlExportable) { |
227 | $this->data = $data->export(); | |
228 | } else { | |
229 | $this->data = json_decode(json_encode($data), true); | |
230 | } | |
701cf973 FB |
231 | } else if (is_array($data)) { |
232 | $this->data = $data; | |
233 | } | |
234 | ||
235 | if (is_null($this->data)) { | |
236 | throw new PlDBBadValueException($data, $field, 'cannot interpret data as json: ' . $data); | |
237 | } | |
238 | } | |
239 | ||
240 | public function format() | |
241 | { | |
242 | return XDB::escape(json_encode($this->data)); | |
243 | } | |
244 | ||
c504af53 FB |
245 | public function export() |
246 | { | |
247 | return $this->data; | |
248 | } | |
249 | ||
701cf973 FB |
250 | public function offsetExists($offset) |
251 | { | |
252 | return isset($this->data[$offset]); | |
253 | } | |
254 | ||
255 | public function offsetGet($offset) | |
256 | { | |
257 | return $this->data[$offset]; | |
258 | } | |
259 | ||
260 | public function offsetSet($offset, $value) | |
261 | { | |
262 | $this->data[$offset] = $value; | |
263 | } | |
264 | ||
265 | public function offsetUnset($offset) | |
266 | { | |
267 | unset($this->data[$offset]); | |
268 | } | |
269 | } | |
270 | ||
26d00fe5 FB |
271 | |
272 | /** This class aims at providing a simple interface to interact with a single | |
273 | * table of a database. It is implemented as a wrapper around XDB. | |
274 | */ | |
275 | class PlDBTable | |
276 | { | |
71db9fda FB |
277 | const PRIMARY_KEY = 'PRIMARY'; |
278 | ||
26d00fe5 FB |
279 | public $table; |
280 | ||
281 | private $schema; | |
71db9fda FB |
282 | private $primaryKey; |
283 | private $uniqueKeys; | |
284 | private $multipleKeys; | |
26d00fe5 FB |
285 | private $mutableFields; |
286 | ||
287 | public function __construct($table) | |
288 | { | |
289 | $this->table = $table; | |
290 | $this->schema(); | |
291 | } | |
292 | ||
71db9fda | 293 | private function parseSchema(PlIterator $schema, PlIterator $keys) |
26d00fe5 FB |
294 | { |
295 | $this->schema = array(); | |
71db9fda FB |
296 | $this->primaryKey = array(); |
297 | $this->uniqueKeys = array(); | |
298 | $this->multipleKeys = array(); | |
26d00fe5 FB |
299 | $this->mutableFields = array(); |
300 | while ($column = $schema->next()) { | |
301 | $field = new PlDBTableField($column); | |
302 | $this->schema[$field->name] = $field; | |
71db9fda | 303 | if (!$field->inPrimaryKey) { |
26d00fe5 FB |
304 | $this->mutableFields[] = $field->name; |
305 | } | |
306 | } | |
71db9fda FB |
307 | while ($column = $keys->next()) { |
308 | $name = $column['Key_name']; | |
309 | $multiple = intval($column['Non_unique']) != 0; | |
310 | $field = $column['Column_name']; | |
311 | if ($multiple) { | |
312 | if (!isset($this->multipleKeys[$name])) { | |
313 | $this->multipleKeys[$name] = array(); | |
314 | } | |
315 | $this->multipleKeys[$name][] = $field; | |
316 | } else if ($name == self::PRIMARY_KEY) { | |
317 | $this->primaryKey[] = $field; | |
318 | } else { | |
319 | if (!isset($this->uniqueKeys[$name])) { | |
320 | $this->uniqueKeys[$name] = array(); | |
321 | } | |
322 | $this->uniqueKeys[$name][] = $field; | |
323 | } | |
324 | } | |
26d00fe5 FB |
325 | } |
326 | ||
327 | ||
328 | private function schema() | |
329 | { | |
330 | if (!$this->schema) { | |
331 | $schema = XDB::iterator('DESCRIBE ' . $this->table); | |
71db9fda FB |
332 | $keys = XDB::iterator('SHOW INDEX FROM ' . $this->table); |
333 | $this->parseSchema($schema, $keys); | |
26d00fe5 FB |
334 | } |
335 | return $this->schema; | |
336 | } | |
337 | ||
338 | private function field($field) | |
339 | { | |
340 | $schema = $this->schema(); | |
341 | if (!isset($schema[$field])) { | |
342 | throw new PlDBNoSuchFieldException($field, $this); | |
343 | } | |
344 | return $schema[$field]; | |
345 | } | |
346 | ||
347 | public function formatField($field, $value) | |
348 | { | |
349 | return $this->field($field)->format($value); | |
350 | } | |
351 | ||
701cf973 FB |
352 | public function registerFieldFormatter($field, $class) |
353 | { | |
354 | return $this->field($field)->registerFormatter($class); | |
355 | } | |
356 | ||
22b7e8d8 FB |
357 | public function registerFieldValidator($field, $class) |
358 | { | |
359 | return $this->field($field)->registerValidator($class); | |
360 | } | |
361 | ||
362 | ||
26d00fe5 FB |
363 | public function defaultValue($field) |
364 | { | |
365 | return $this->field($field)->defaultValue; | |
366 | } | |
367 | ||
71db9fda FB |
368 | private function hasKeyField(PlDBTableEntry $entry, array $fields) |
369 | { | |
370 | foreach ($fields as $field) { | |
371 | if (isset($entry->$field)) { | |
372 | return true; | |
373 | } | |
374 | } | |
375 | return false; | |
376 | } | |
377 | ||
378 | private function keyFields($keyName) | |
379 | { | |
380 | if ($keyName == self::PRIMARY_KEY) { | |
381 | return $this->primaryKey; | |
382 | } else if (isset($this->uniqueKeys[$keyName])) { | |
383 | return $this->uniqueKeys[$keyName]; | |
384 | } else if (isset($this->multipleKeys[$keyName])) { | |
385 | return $this->multipleKeys[$keyName]; | |
386 | } | |
387 | throw new PlDBNoSuchKeyException($keyName, $this); | |
388 | } | |
389 | ||
390 | private function bestKeyFields(PlDBTableEntry $entry, $allowMultiple) | |
391 | { | |
392 | if ($this->hasKeyField($entry, $this->primaryKey)) { | |
393 | return $this->primaryKey; | |
394 | } | |
395 | foreach ($this->uniqueKeys as $fields) { | |
396 | if ($this->hasKeyField($entry, $fields)) { | |
397 | return $fields; | |
398 | } | |
399 | } | |
400 | if ($allowMultiple) { | |
401 | foreach ($this->multipleKeys as $fields) { | |
402 | if ($this->hasKeyField($entry, $fields)) { | |
403 | return $fields; | |
404 | } | |
405 | } | |
406 | } | |
407 | return $this->primaryKey; | |
408 | } | |
409 | ||
410 | public function key(PlDBTableEntry $entry, array $keyFields) | |
26d00fe5 FB |
411 | { |
412 | $key = array(); | |
71db9fda | 413 | foreach ($keyFields as $field) { |
26d00fe5 FB |
414 | if (!isset($entry->$field)) { |
415 | throw new PlDBIncompleteEntryDescription($field, $this); | |
416 | } else { | |
417 | $key[] = XDB::escape($this->$field); | |
418 | } | |
419 | } | |
420 | return implode('-', $key); | |
421 | } | |
422 | ||
71db9fda FB |
423 | public function primaryKey(PlDBTableEntry $entry) |
424 | { | |
425 | return $this->key($this->keyFields(self::PRIMARY_KEY)); | |
426 | } | |
427 | ||
428 | private function buildKeyCondition(PlDBTableEntry $entry, array $keyFields, $allowIncomplete) | |
26d00fe5 FB |
429 | { |
430 | $condition = array(); | |
71db9fda | 431 | foreach ($keyFields as $field) { |
26d00fe5 FB |
432 | if (!isset($entry->$field)) { |
433 | if (!$allowIncomplete) { | |
434 | throw new PlDBIncompleteEntryDescription($field, $this); | |
435 | } | |
436 | } else { | |
437 | $condition[] = XDB::format($field . ' = {?}', $entry->$field); | |
438 | } | |
439 | } | |
440 | return implode(' AND ', $condition); | |
441 | } | |
442 | ||
443 | public function fetchEntry(PlDBTableEntry $entry) | |
444 | { | |
445 | $result = XDB::rawFetchOneAssoc('SELECT * | |
446 | FROM ' . $this->table . ' | |
71db9fda FB |
447 | WHERE ' . $this->buildKeyCondition($entry, |
448 | $this->bestKeyFields($entry, false), | |
449 | false)); | |
26d00fe5 FB |
450 | if (!$result) { |
451 | return false; | |
452 | } | |
453 | return $entry->fillFromDBData($result); | |
454 | } | |
455 | ||
71db9fda | 456 | public function iterateOnCondition(PlDBTableEntry $entry, $condition, $sortField) |
26d00fe5 | 457 | { |
71db9fda FB |
458 | if (empty($sortField)) { |
459 | $sortField = $this->primaryKey; | |
20030248 | 460 | } |
71db9fda FB |
461 | if (!is_array($sortField)) { |
462 | $sortField = array($sortField); | |
463 | } | |
464 | $sort = ' ORDER BY ' . implode(', ', $sortField); | |
26d00fe5 FB |
465 | $it = XDB::rawIterator('SELECT * |
466 | FROM ' . $this->table . ' | |
71db9fda FB |
467 | WHERE ' . $condition . ' |
468 | ' . $sort); | |
26d00fe5 FB |
469 | return PlIteratorUtils::map($it, array($entry, 'cloneAndFillFromDBData')); |
470 | } | |
471 | ||
71db9fda FB |
472 | public function iterateOnEntry(PlDBTableEntry $entry, $sortField) |
473 | { | |
474 | return $this->iterateOnCondition($entry, | |
475 | $this->buildKeyCondition($entry, | |
476 | $this->bestKeyFields($entry, true), | |
477 | true), | |
478 | $sortField); | |
479 | } | |
480 | ||
20030248 FB |
481 | const SAVE_INSERT_MISSING = 0x01; |
482 | const SAVE_UPDATE_EXISTING = 0x02; | |
483 | const SAVE_IGNORE_DUPLICATE = 0x04; | |
cb22cf2a | 484 | public function saveEntries(array $entries, $flags) |
26d00fe5 | 485 | { |
20030248 FB |
486 | $flags &= (self::SAVE_INSERT_MISSING | self::SAVE_UPDATE_EXISTING | self::SAVE_IGNORE_DUPLICATE); |
487 | Platal::assert($flags != 0, "Hey, the flags ($flags) here are so stupid, don't know what to do"); | |
488 | if ($flags == self::SAVE_UPDATE_EXISTING) { | |
cb22cf2a FB |
489 | foreach ($entries as $entry) { |
490 | $values = array(); | |
491 | foreach ($this->mutableFields as $field) { | |
492 | if ($entry->hasChanged($field)) { | |
493 | $values[] = XDB::format($field . ' = {?}', $entry->$field); | |
494 | } | |
495 | } | |
496 | if (count($values) > 0) { | |
497 | XDB::rawExecute('UPDATE ' . $this->table . ' | |
498 | SET ' . implode(', ', $values) . ' | |
499 | WHERE ' . $this->buildKeyCondition($entry, | |
500 | $this->keyFields(self::PRIMARY_KEY), | |
501 | false)); | |
20030248 | 502 | } |
20030248 FB |
503 | } |
504 | } else { | |
cb22cf2a FB |
505 | $fields = new PlFlagSet(); |
506 | foreach ($entries as $entry) { | |
507 | foreach ($this->schema as $field=>$type) { | |
508 | if ($type->inPrimaryKey || $entry->hasChanged($field)) { | |
509 | $fields->addFlag($field); | |
510 | } | |
20030248 FB |
511 | } |
512 | } | |
cb22cf2a FB |
513 | if (count($fields->export()) > 0) { |
514 | foreach ($entries as $entry) { | |
515 | $v = array(); | |
516 | foreach ($fields as $field) { | |
517 | $v[$field] = XDB::escape($entry->$field); | |
518 | } | |
519 | $values[] = '(' . implode(', ', $v) . ')'; | |
520 | } | |
521 | ||
522 | $query = $this->table . ' (' . implode(', ', $fields->export()) . ') | |
523 | VALUES ' . implode(",\n", $values); | |
20030248 FB |
524 | if (($flags & self::SAVE_UPDATE_EXISTING)) { |
525 | $update = array(); | |
526 | foreach ($this->mutableFields as $field) { | |
527 | if (isset($values[$field])) { | |
528 | $update[] = "$field = VALUES($field)"; | |
529 | } | |
530 | } | |
531 | if (count($update) > 0) { | |
cb22cf2a | 532 | $query = 'INSERT INTO ' . $query; |
20030248 FB |
533 | $query .= "\n ON DUPLICATE KEY UPDATE " . implode(', ', $update); |
534 | } else { | |
cb22cf2a | 535 | $query = 'INSERT IGNORE INTO ' . $query; |
20030248 FB |
536 | } |
537 | } else if (($flags & self::SAVE_IGNORE_DUPLICATE)) { | |
cb22cf2a | 538 | $query = 'INSERT IGNORE INTO ' . $query; |
20030248 | 539 | } else { |
cb22cf2a | 540 | $query = 'INSERT INTO ' . $query; |
20030248 FB |
541 | } |
542 | XDB::rawExecute($query); | |
cb22cf2a FB |
543 | if (count($entries) == 1) { |
544 | $id = XDB::insertId(); | |
545 | if ($id) { | |
546 | $entry = end($entries); | |
547 | foreach ($this->primaryKey as $field) { | |
548 | if ($this->schema[$field]->autoIncrement) { | |
549 | $entry->$field = $id; | |
550 | break; | |
551 | } | |
20030248 FB |
552 | } |
553 | } | |
554 | } | |
26d00fe5 FB |
555 | } |
556 | } | |
20030248 FB |
557 | } |
558 | ||
559 | public function deleteEntry(PlDBTableEntry $entry, $allowIncomplete) | |
560 | { | |
561 | XDB::rawExecute('DELETE FROM ' . $this->table . ' | |
71db9fda FB |
562 | WHERE ' . $this->buildKeyCondition($entry, |
563 | $this->bestKeyFields($entry, $allowIncomplete), | |
564 | $allowIncomplete)); | |
26d00fe5 FB |
565 | } |
566 | ||
c504af53 FB |
567 | public function exportEntry(PlDBTableEntry $entry) |
568 | { | |
569 | $export = array(); | |
570 | foreach ($this->schema as $key=>$field) { | |
571 | $value = $entry->$key; | |
572 | if ($value instanceof PlExportable) { | |
573 | $value = $value->export(); | |
574 | } | |
575 | $export[$key] = $value; | |
576 | } | |
577 | return $export; | |
578 | } | |
579 | ||
26d00fe5 FB |
580 | public static function get($name) |
581 | { | |
26d00fe5 FB |
582 | return new PlDBTable($name); |
583 | } | |
584 | } | |
585 | ||
c504af53 | 586 | class PlDBTableEntry extends PlAbstractIterable implements PlExportable |
26d00fe5 FB |
587 | { |
588 | private $table; | |
589 | private $changed; | |
590 | private $fetched = false; | |
591 | private $autoFetch; | |
592 | ||
593 | private $data = array(); | |
594 | ||
595 | public function __construct($table, $autoFetch = false) | |
596 | { | |
597 | if ($table instanceof PlDBTable) { | |
598 | $this->table = $table; | |
599 | } else { | |
600 | $this->table = PlCache::getGlobal('pldbtable_' . $table, array('PlDBTable', 'get'), array($table)); | |
601 | } | |
602 | $this->autoFetch = $autoFetch; | |
603 | $this->changed = new PlFlagSet(); | |
604 | } | |
605 | ||
701cf973 FB |
606 | /** Register a custom formatter for a field. |
607 | * | |
608 | * A formatter can be used to perform on-the-fly conversion from db storage to a user-friendly format. | |
609 | * For example, if you have a textual field that contain json, you can use a JSonFieldFormatter on this | |
610 | * field to perform automatic decoding when reading from the database (or when assigning the field) | |
611 | * and automatic json_encoding when storing the object back to the db. | |
612 | */ | |
613 | protected function registerFieldFormatter($field, $formatterClass) | |
614 | { | |
615 | $this->table->registerFieldFormatter($field, $formatterClass); | |
616 | } | |
617 | ||
22b7e8d8 FB |
618 | /** Register a custom validator for a field. |
619 | * | |
620 | * A validator perform a pre-filter on the value of a field. As opposed to the formatters, it does | |
621 | * not affects how the value is stored in the database. | |
622 | */ | |
623 | protected function registerFieldValidator($field, $validatorClass) | |
624 | { | |
625 | $this->table->registerFieldValidator($field, $validatorClass); | |
626 | } | |
627 | ||
26d00fe5 FB |
628 | /** This hook is called when the entry is going to be updated in the db. |
629 | * | |
630 | * A typical usecase is a class that stores low-level representation of | |
631 | * an object in db and perform a conversion between this low-level representation | |
632 | * and a higher-level representation. | |
633 | * | |
634 | * @return true in case of success | |
635 | */ | |
636 | protected function preSave() | |
637 | { | |
638 | return true; | |
639 | } | |
640 | ||
20030248 FB |
641 | /** This hook is called when the entry has been save in the database. |
642 | * | |
643 | * It can be used to perform post-actions on save like storing extra data | |
644 | * in database or sending a notification. | |
645 | */ | |
646 | protected function postSave() | |
647 | { | |
648 | } | |
649 | ||
650 | /** This hook is called when the entry is going to be deleted from the db. | |
651 | * | |
652 | * Default behavior is to call preSave(). | |
653 | * | |
654 | * @return true in case of success. | |
655 | */ | |
656 | protected function preDelete() | |
657 | { | |
658 | return $this->preSave(); | |
659 | } | |
660 | ||
26d00fe5 FB |
661 | /** This hook is called when the entry has just been fetched from the db. |
662 | * | |
663 | * This is the counterpart of @ref preSave and a typical use-case is the conversion | |
664 | * from a high-level representation of the objet to a representation suitable for | |
665 | * storage in the database. | |
666 | * | |
667 | * @return true in case of success. | |
668 | */ | |
669 | protected function postFetch() | |
670 | { | |
671 | return true; | |
672 | } | |
673 | ||
674 | public function __get($field) | |
675 | { | |
676 | if (isset($this->data[$field])) { | |
677 | return $this->data[$field]; | |
678 | } else if (!$this->fetched && $this->autoFetch) { | |
679 | $this->fetch(); | |
680 | if (isset($this->data[$field])) { | |
681 | return $this->data[$field]; | |
682 | } | |
683 | } | |
684 | return $this->table->defaultValue($field); | |
685 | } | |
686 | ||
687 | public function __set($field, $value) | |
688 | { | |
689 | $this->data[$field] = $this->table->formatField($field, $value); | |
690 | $this->changed->addFlag($field); | |
691 | } | |
692 | ||
693 | public function __isset($field) | |
694 | { | |
695 | return isset($this->data[$field]); | |
696 | } | |
697 | ||
698 | public function primaryKey() | |
699 | { | |
700 | $this->table->primaryKey($this); | |
701 | } | |
702 | ||
703 | public function hasChanged($field) | |
704 | { | |
705 | return $this->changed->hasFlag($field); | |
706 | } | |
707 | ||
708 | public function fillFromArray(array $data) | |
709 | { | |
710 | foreach ($data as $field => $value) { | |
711 | $this->$field = $value; | |
712 | } | |
713 | } | |
714 | ||
715 | public function fillFromDBData(array $data) | |
716 | { | |
717 | $this->fillFromArray($data); | |
718 | $this->changed->clear(); | |
719 | return $this->postFetch(); | |
720 | } | |
721 | ||
20030248 FB |
722 | public function copy(PlDBTableEntry $other) |
723 | { | |
724 | Platal::assert($this->table == $other->table, | |
90e41060 | 725 | "Trying to fill an entry of table {$this->table->table} with content of {$other->table->table}."); |
20030248 FB |
726 | $this->changed = $other->changed; |
727 | $this->fetched = $other->fetched; | |
728 | $this->data = $other->data; | |
729 | } | |
730 | ||
26d00fe5 FB |
731 | public function cloneAndFillFromDBData(array $data) |
732 | { | |
733 | $clone = clone $this; | |
734 | $clone->fillFromDBData($data); | |
735 | return $clone; | |
736 | } | |
737 | ||
738 | public function fetch() | |
739 | { | |
740 | return $this->table->fetchEntry($this); | |
741 | } | |
742 | ||
20030248 | 743 | public function iterate($sortField = null) |
26d00fe5 | 744 | { |
20030248 | 745 | return $this->table->iterateOnEntry($this, $sortField); |
26d00fe5 FB |
746 | } |
747 | ||
71db9fda FB |
748 | public function iterateOnCondition($condition, $sortField = null) |
749 | { | |
750 | return $this->table->iterateOnCondition($this, $condition, $sortField); | |
751 | } | |
752 | ||
20030248 | 753 | public function save($flags) |
26d00fe5 | 754 | { |
cb22cf2a | 755 | return self::saveBatch(array($this), $flags); |
26d00fe5 | 756 | } |
20030248 FB |
757 | |
758 | public function update($insertMissing = false) | |
759 | { | |
760 | $flags = PlDBTable::SAVE_UPDATE_EXISTING; | |
761 | if ($insertMissing) { | |
762 | $flags = PlDBTable::SAVE_INSERT_MISSING; | |
763 | } | |
764 | return $this->save($flags); | |
765 | } | |
766 | ||
767 | public function insert($allowUpdate = false) | |
768 | { | |
769 | $flags = PlDBTable::SAVE_INSERT_MISSING; | |
770 | if ($allowUpdate) { | |
771 | $flags |= PlDBTable::SAVE_UPDATE_EXISTING; | |
772 | } | |
773 | return $this->save($flags); | |
774 | } | |
775 | ||
776 | public function delete() | |
777 | { | |
778 | if (!$this->preDelete()) { | |
779 | return 0; | |
780 | } | |
781 | return $this->table->deleteEntry($this, true); | |
782 | } | |
c504af53 FB |
783 | |
784 | public function export() | |
785 | { | |
786 | return $this->table->exportEntry($this); | |
787 | } | |
cb22cf2a FB |
788 | |
789 | protected static function saveBatch($entries, $flags) | |
790 | { | |
791 | $table = null; | |
792 | foreach ($entries as $entry) { | |
793 | if (is_null($table)) { | |
794 | $table = $entry->table; | |
795 | } else { | |
796 | Platal::assert($table === $entry->table, "Cannot save batch of entries of different kinds"); | |
797 | } | |
798 | if (!$entry->preSave()) { | |
799 | return false; | |
800 | } | |
801 | } | |
802 | $table->saveEntries($entries, $flags); | |
803 | foreach ($entries as $entry) { | |
804 | $entry->changed->clear(); | |
805 | $entry->postSave(); | |
806 | } | |
807 | return true; | |
808 | } | |
809 | ||
810 | public static function insertBatch($entries, $allowUpdate = false) | |
811 | { | |
812 | $flags = PlDBTable::SAVE_INSERT_MISSING; | |
813 | if ($allowUpdate) { | |
814 | $flags |= PlDBTable::SAVE_UPDATE_EXISTING; | |
815 | } | |
816 | return self::saveBatch($entries, $flags); | |
817 | } | |
26d00fe5 FB |
818 | } |
819 | ||
820 | // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: | |
821 | ?> |