Take into account PlExportable interface.
[platal.git] / classes / pldbtableentry.php
CommitLineData
26d00fe5
FB
1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
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
22class 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
33class 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
42class 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
52class 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
62class 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 168interface PlDBTableFieldValidator
701cf973
FB
169{
170 public function __construct(PlDBTableField $field, $value);
171}
172
c504af53 173interface PlDBTableFieldFormatter extends PlDBTableFieldValidator, XDBFormat, PlExportable
22b7e8d8
FB
174{
175}
176
701cf973 177class 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
213class 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 */
275class 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;
484 public function saveEntry(PlDBTableEntry $entry, $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) {
489 $values = array();
490 foreach ($this->mutableFields as $field) {
491 if ($entry->hasChanged($field)) {
492 $values[] = XDB::format($field . ' = {?}', $entry->$field);
493 }
494 }
495 if (count($values) > 0) {
496 XDB::rawExecute('UPDATE ' . $this->table . '
497 SET ' . implode(', ', $values) . '
71db9fda
FB
498 WHERE ' . $this->buildKeyCondition($entry,
499 $this->keyFields(self::PRIMARY_KEY),
500 false));
20030248
FB
501 }
502 } else {
503 $values = array();
504 foreach ($this->schema as $field=>$type) {
505 if ($entry->hasChanged($field)) {
506 $values[$field] = XDB::escape($entry->$field);
507 }
508 }
509 if (count($values) > 0) {
510 $query = $this->table . ' (' . implode(', ', array_keys($values)) . ')
511 VALUES (' . implode(', ', $values) . ')';
512 if (($flags & self::SAVE_UPDATE_EXISTING)) {
513 $update = array();
514 foreach ($this->mutableFields as $field) {
515 if (isset($values[$field])) {
516 $update[] = "$field = VALUES($field)";
517 }
518 }
519 if (count($update) > 0) {
520 $query = 'INSERT ' . $query;
521 $query .= "\n ON DUPLICATE KEY UPDATE " . implode(', ', $update);
522 } else {
523 $query = 'INSERT IGNORE ' . $query;
524 }
525 } else if (($flags & self::SAVE_IGNORE_DUPLICATE)) {
526 $query = 'INSERT IGNORE ' . $query;
527 } else {
528 $query = 'INSERT ' . $query;
529 }
530 XDB::rawExecute($query);
531 $id = XDB::insertId();
532 if ($id) {
71db9fda 533 foreach ($this->primaryKey as $field) {
20030248
FB
534 if ($this->schema[$field]->autoIncrement) {
535 $entry->$field = $id;
536 break;
537 }
538 }
539 }
26d00fe5
FB
540 }
541 }
20030248
FB
542 }
543
544 public function deleteEntry(PlDBTableEntry $entry, $allowIncomplete)
545 {
546 XDB::rawExecute('DELETE FROM ' . $this->table . '
71db9fda
FB
547 WHERE ' . $this->buildKeyCondition($entry,
548 $this->bestKeyFields($entry, $allowIncomplete),
549 $allowIncomplete));
26d00fe5
FB
550 }
551
c504af53
FB
552 public function exportEntry(PlDBTableEntry $entry)
553 {
554 $export = array();
555 foreach ($this->schema as $key=>$field) {
556 $value = $entry->$key;
557 if ($value instanceof PlExportable) {
558 $value = $value->export();
559 }
560 $export[$key] = $value;
561 }
562 return $export;
563 }
564
26d00fe5
FB
565 public static function get($name)
566 {
26d00fe5
FB
567 return new PlDBTable($name);
568 }
569}
570
c504af53 571class PlDBTableEntry extends PlAbstractIterable implements PlExportable
26d00fe5
FB
572{
573 private $table;
574 private $changed;
575 private $fetched = false;
576 private $autoFetch;
577
578 private $data = array();
579
580 public function __construct($table, $autoFetch = false)
581 {
582 if ($table instanceof PlDBTable) {
583 $this->table = $table;
584 } else {
585 $this->table = PlCache::getGlobal('pldbtable_' . $table, array('PlDBTable', 'get'), array($table));
586 }
587 $this->autoFetch = $autoFetch;
588 $this->changed = new PlFlagSet();
589 }
590
701cf973
FB
591 /** Register a custom formatter for a field.
592 *
593 * A formatter can be used to perform on-the-fly conversion from db storage to a user-friendly format.
594 * For example, if you have a textual field that contain json, you can use a JSonFieldFormatter on this
595 * field to perform automatic decoding when reading from the database (or when assigning the field)
596 * and automatic json_encoding when storing the object back to the db.
597 */
598 protected function registerFieldFormatter($field, $formatterClass)
599 {
600 $this->table->registerFieldFormatter($field, $formatterClass);
601 }
602
22b7e8d8
FB
603 /** Register a custom validator for a field.
604 *
605 * A validator perform a pre-filter on the value of a field. As opposed to the formatters, it does
606 * not affects how the value is stored in the database.
607 */
608 protected function registerFieldValidator($field, $validatorClass)
609 {
610 $this->table->registerFieldValidator($field, $validatorClass);
611 }
612
26d00fe5
FB
613 /** This hook is called when the entry is going to be updated in the db.
614 *
615 * A typical usecase is a class that stores low-level representation of
616 * an object in db and perform a conversion between this low-level representation
617 * and a higher-level representation.
618 *
619 * @return true in case of success
620 */
621 protected function preSave()
622 {
623 return true;
624 }
625
20030248
FB
626 /** This hook is called when the entry has been save in the database.
627 *
628 * It can be used to perform post-actions on save like storing extra data
629 * in database or sending a notification.
630 */
631 protected function postSave()
632 {
633 }
634
635 /** This hook is called when the entry is going to be deleted from the db.
636 *
637 * Default behavior is to call preSave().
638 *
639 * @return true in case of success.
640 */
641 protected function preDelete()
642 {
643 return $this->preSave();
644 }
645
26d00fe5
FB
646 /** This hook is called when the entry has just been fetched from the db.
647 *
648 * This is the counterpart of @ref preSave and a typical use-case is the conversion
649 * from a high-level representation of the objet to a representation suitable for
650 * storage in the database.
651 *
652 * @return true in case of success.
653 */
654 protected function postFetch()
655 {
656 return true;
657 }
658
659 public function __get($field)
660 {
661 if (isset($this->data[$field])) {
662 return $this->data[$field];
663 } else if (!$this->fetched && $this->autoFetch) {
664 $this->fetch();
665 if (isset($this->data[$field])) {
666 return $this->data[$field];
667 }
668 }
669 return $this->table->defaultValue($field);
670 }
671
672 public function __set($field, $value)
673 {
674 $this->data[$field] = $this->table->formatField($field, $value);
675 $this->changed->addFlag($field);
676 }
677
678 public function __isset($field)
679 {
680 return isset($this->data[$field]);
681 }
682
683 public function primaryKey()
684 {
685 $this->table->primaryKey($this);
686 }
687
688 public function hasChanged($field)
689 {
690 return $this->changed->hasFlag($field);
691 }
692
693 public function fillFromArray(array $data)
694 {
695 foreach ($data as $field => $value) {
696 $this->$field = $value;
697 }
698 }
699
700 public function fillFromDBData(array $data)
701 {
702 $this->fillFromArray($data);
703 $this->changed->clear();
704 return $this->postFetch();
705 }
706
20030248
FB
707 public function copy(PlDBTableEntry $other)
708 {
709 Platal::assert($this->table == $other->table,
90e41060 710 "Trying to fill an entry of table {$this->table->table} with content of {$other->table->table}.");
20030248
FB
711 $this->changed = $other->changed;
712 $this->fetched = $other->fetched;
713 $this->data = $other->data;
714 }
715
26d00fe5
FB
716 public function cloneAndFillFromDBData(array $data)
717 {
718 $clone = clone $this;
719 $clone->fillFromDBData($data);
720 return $clone;
721 }
722
723 public function fetch()
724 {
725 return $this->table->fetchEntry($this);
726 }
727
20030248 728 public function iterate($sortField = null)
26d00fe5 729 {
20030248 730 return $this->table->iterateOnEntry($this, $sortField);
26d00fe5
FB
731 }
732
71db9fda
FB
733 public function iterateOnCondition($condition, $sortField = null)
734 {
735 return $this->table->iterateOnCondition($this, $condition, $sortField);
736 }
737
20030248 738 public function save($flags)
26d00fe5
FB
739 {
740 if (!$this->preSave()) {
741 return false;
742 }
20030248 743 $this->table->saveEntry($this, $flags);
26d00fe5 744 $this->changed->clear();
20030248 745 $this->postSave();
26d00fe5
FB
746 return true;
747 }
20030248
FB
748
749 public function update($insertMissing = false)
750 {
751 $flags = PlDBTable::SAVE_UPDATE_EXISTING;
752 if ($insertMissing) {
753 $flags = PlDBTable::SAVE_INSERT_MISSING;
754 }
755 return $this->save($flags);
756 }
757
758 public function insert($allowUpdate = false)
759 {
760 $flags = PlDBTable::SAVE_INSERT_MISSING;
761 if ($allowUpdate) {
762 $flags |= PlDBTable::SAVE_UPDATE_EXISTING;
763 }
764 return $this->save($flags);
765 }
766
767 public function delete()
768 {
769 if (!$this->preDelete()) {
770 return 0;
771 }
772 return $this->table->deleteEntry($this, true);
773 }
c504af53
FB
774
775 public function export()
776 {
777 return $this->table->exportEntry($this);
778 }
26d00fe5
FB
779}
780
781// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
782?>