AOOSModel: Changed default cache mode and changed order of parametres to setProperties.
[AOOS.git] / AOOSModel.php
blobdd490afd48bf4729907dcbec72d6324be436196b
1 <?php
2 require_once("lib/AOOSModelConstraints.php");
4 define('AOOSMODEL_TYPE_STRING', 1);
5 define('AOOSMODEL_TYPE_TEXT', 2);
6 define('AOOSMODEL_TYPE_INTEGER', 3);
7 define('AOOSMODEL_TYPE_BOOLEAN', 4);
8 define('AOOSMODEL_TYPE_UNKNOWN', 5);
10 define('AOOSMODEL_PROP_HASH', 1);
11 define('AOOSMODEL_PROP_NOHTML', 2);
12 define('AOOSMODEL_PROP_STRIP', 4);
13 define('AOOSMODEL_PROP_ESCAPE', 8);
14 define('AOOSMODEL_PROP_TIME', 16);
16 define('AOOSMODEL_FLAG_FROM_DATABASE', 1);
17 define('AOOSMODEL_FLAG_UNIQUE', 2);
18 define('AOOSMODEL_FLAG_PRIMARY_KEY', 4);
19 define('AOOSMODEL_FLAG_GUI_PRIVATE', 8);
20 define('AOOSMODEL_FLAG_GUI_NOTEDITABLE',16);
22 define('AOOSMODELROW_PRIVATE_INSERT', 1);
23 define('AOOSMODELROW_PRIVATE_UPDATE', 2);
24 define('AOOSMODELROW_PRIVATE_REMOVE', 3);
26 define('AOOSMODEL_CACHE_STATIC', 1); // Only populate if empty
27 define('AOOSMODEL_CACHE_DYNAMIC', 2); // Clear and populate always
29 class AOOSModel extends AOOSModule {
30 private $_data = array();
31 private $_columnIndex = array();
32 private $_properties = array();
33 private $_constraints = array();
34 private $_types = array();
35 private $_flags = array();
36 private $_defaults = array();
37 private $_storage = null;
38 private $_rows = 0;
39 private $_lastMatch = 0;
40 private $_cache = AOOSMODEL_CACHE_DYNAMIC;
42 private $_table = null;
44 /* ------ Data functions ------ */
45 /**
46 * Appends a row to the end of the model. If given an array, the function creates the row with the values specified
47 * in the array. Otherwise the row is empty.
48 * @param $values An array with the values to fill the row with. Array keys are column indeces and array values are
49 * the values used to fill the array.
50 * @return true
52 public function appendRow($values = null) {
53 if ($values instanceof AOOSModelRow) {
54 $values = $values->toArray();
56 $row = new AOOSModelRow($this, $values, AOOSMODELROW_PRIVATE_INSERT);
57 $this->_data[] = $row;
58 $this->_rows++;
59 return true;
62 /**
63 * Sets value of $column in row $row to be $value. Throws an AOOSException if $row isn't valid, if the specified
64 * column isn't a valid column index or setting the value for some reason failed.
65 * @param $row An integer representing the row
66 * @param $column A valid column index.
67 * @param $value The value
68 * @return boolean
70 public function setData($row, $column, $value) {
71 try {
72 $r = $this->getRow($row);
73 $ret = $r->$column = $value;
74 } catch (AOOSException $e) {
75 throw $e;
77 return $ret;
80 /**
81 * Returns all rows that haven't been removed
82 * @return array
84 public function data() {
85 $rows = array();
86 foreach ($this->_data as $row) {
87 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE) {
88 $rows[] = $row;
91 return $rows;
94 /**
95 * Returns the row at $index position. If a negative value is given it is counted from the end. Throws an AOOSException
96 * if $index isn't within bounds.
97 * @param $index An integer representing the row
98 * @sa getRows
99 * @return AOOSModelRow
101 public function getRow($index) {
102 if ($index < 0) {
103 $index = $this->rows()+$index;
105 if($index >= $this->rows() || $index < 0) {
106 throw new AOOSException($this->core(), $this->tr("bound_error"), $index);
108 return $this->_data[$index];
112 * Returns all rows from $start until $length rows is found. If $start is negative, it is counted from the end. Throws an AOOSException if $start or $start+$length aren't within bounds.
113 * @param $start The start row. An integer.
114 * @param $length Get $length number of rows. An integer.
115 * @sa getRows
116 * @return array
118 public function getRows($start, $length) {
119 $rows = array();
120 if ($start < 0) {
121 $start = $this->rows()+$start;
123 elseif ($start > $this->rows() || $start+$length > $this->rows()) {
124 throw new AOOSException($this->core(), $this->tr("bound_error"));
126 for ($i=0; $i<$length; ++$i) {
127 $rows[$start] = $this->_data[$start];
128 $start++;
130 return $rows;
134 * Returns the number of rows.
135 * @return integer
137 public function rows() {
138 return $this->_rows;
142 * Removes rows that match $where. $limit defines the number of rows to remove. Both $where and $limit are arrays.
143 * $limit consists of two entries, $limit[0] which defines the start of which rows to remove and $limit[1] which
144 * defines the number of rows to remove. Throws an AOOSException if $where or $limit aren't arrays.
145 * @param $where An array against which all rows are matched.
146 * @param $limit The limit array.
147 * @return true.
149 public function remove($where, $limit) {
150 if (!is_array($where) || !is_array($limit)) {
151 throw new AOOSException($this->core(), $this->tr("not_array"));
153 $rows = $this->findAll($where);
154 $rows = array_slice($rows, $limit[0], $limit[1]);
155 foreach ($rows as $row) {
156 $row->setFlag(AOOSMODELROW_PRIVATE_REMOVE);
158 return true;
162 * Returns the column $column. $start defines the start row and $length defines the number of rows. $start and
163 * $length are optional. Throws an AOOSException if $column isn't a valid column index.
164 * @param $column The column name.
165 * @param $start An integer representing the start row. Defaults to 0.
166 * @param $length The number or rows to use. Defaults to all rows.
167 * @return array
169 public function getColumn($column, $start = 0, $length = null) {
170 $columnA = array();
171 if (!$this->inColumnIndex($column)) {
172 throw new AOOSException($this->core(), $this->tr("index_error"));
174 if ($length === null) {
175 $length = $this->rows();
177 $rows = $this->getRows($start, $length);
178 foreach ($rows as $index => $row) {
179 $columnA[$index] = $row[$column];
181 return $columnA;
185 * Returns the first row that matches $match from $offset and forwards. The rows are matched using preg_match and
186 * delimiters should NOT be included in the array values. Throws an AOOSException if $offset isn't within bounds.
187 * @param $match An array in which keys are column names and values are the values to match against.
188 * @param $offset The row to start searching in.
189 * @sa findAll
190 * @return AOOSModelRow or false if no rows are found.
192 public function find($match, $offset = 0) {
193 $m = false;
194 for($i = $offset; $i < $this->rows(); ++$i) {
195 try {
196 $row = $this->getRow($i);
197 if ($row->match($match) !== false) {
198 $m = $row;
199 $this->_lastMatch = $i;
200 break;
202 } catch (AOOSException $e) {
203 throw $e;
206 return $m;
210 * Returns all rows that match $match from $offset and forwards. Throws an AOOSException if $offset isn't within
211 * bounds.
212 * @param $match An array in which keys are column names and values are the values to match against.
213 * @param $offset The row to start searching in.
214 * @sa find
215 * @return array
217 public function findAll($match, $offset = 0) {
218 $m = array();
219 $i = 0;
220 while (true) {
221 try {
222 $row = $this->find($match, $i);
223 } catch (AOOSException $e) {
224 throw $e;
226 if ($row === false) {
227 break;
229 $m[] = $row;
230 $i = $this->_lastMatch+1;
232 return $m;
236 * Empties the model and removes all propeties, constraints and flags. Only column indeces are kept.
237 * @sa resetData
238 * @return true
240 public function reset() {
241 $this->_data = array();
242 $this->_properties = array();
243 $this->_constraints = array();
244 $this->_flags = array();
245 $this->_defaults = array_fill_keys($this->columnIndex(), null);
246 $this->_rows = 0;
247 return true;
251 * Empties the model.
252 * @sa reset
253 * @return true;
255 public function resetData() {
256 $this->_data = array();
257 $this->_rows = 0;
258 return true;
261 /* ----- Cache mode ------ */
262 public function setCacheMode($cacheMode) {
263 if ($cacheMode != AOOSMODEL_CACHE_STATIC && $cacheMode != AOOSMODEL_CACHE_DYNAMIC) {
264 throw new AOOSExcpetion($this->core(), $this->tr("not_valid_cache_mode"));
266 $this->_cache = $cacheMode;
267 return true;
270 public function cacheMode() {
271 return $this->_cache;
275 /* ----- Column index ------ */
276 public function setColumnIndex($columnindex) {
277 if (!is_array($columnindex)) {
278 throw new AOOSException($this->core(), $this->tr("not_array"));
280 $this->_columnIndex = $columnindex;
281 $this->reset();
282 return true;
285 public function columnIndex() {
286 return $this->_columnIndex;
289 public function inColumnIndex($column) {
290 return in_array($column, $this->columnIndex());
294 /* ------ Properties, types, flags and constraints ------ */
295 public function setProperties($column, $type, $flag = 0, $properties = 0) {
296 if (!$this->inColumnIndex($column)) {
297 throw new AOOSException($this->core(), $this->tr("index_error"));
299 if (!is_integer($properties) || !is_integer($type) || !is_integer($flag)) {
300 throw new AOOSException($this->core(), $this->tr("not_integer"));
302 $this->_properties[$column] = $properties;
303 $this->_types[$column] = $type;
304 $this->_flags[$column] = $flag;
305 return null;
308 public function getProperties($column) {
309 if (!$this->inColumnIndex($column)) {
310 throw new AOOSException($this->core(), $this->tr("index_error"));
312 if (!in_array($column, array_keys($this->_properties))) {
313 return false;
315 return $this->_properties[$column];
318 public function addConstraint($column, $constraint, $args) { // XXX Variable argument count
319 if (!$this->inColumnIndex($column)) {
320 throw new AOOSException($this->core(), $this->tr("index_error"));
322 $name = $constraint."Constraint";
323 if (!class_exists($name)) {
324 return false;
326 if (!is_array($args)) {
327 $args = array($args);
329 $numArgs = call_user_func(array($name, "numArgs"));
330 if (count($args) != $numArgs) {
331 return false;
333 if (!in_array($column,array_keys($this->_constraints))) {
334 $this->_constraints[$column] = array();
336 $this->_constraints[$column][] = array($name, $args);
337 return true;
340 public function getConstraints($column) {
341 if (!$this->inColumnIndex($column)) {
342 throw new AOOSException($this->core(), $this->tr("index_error"));
344 if (!in_array($column, array_keys($this->_constraints))) {
345 return false;
347 return $this->_constraints[$column];
351 * Returns the type of $column.
352 * @param Column name.
353 * @return integer
355 public function getType($column) {
356 if (!$this->inColumnIndex($column)) {
357 throw new AOOSException($this->core(), $this->tr("index_error"));
359 return $this->_types[$column];
362 public function setFlags($column, $flag) {
363 if (!$this->inColumnIndex($column)) {
364 throw new AOOSException($this->core(), $this->tr("index_error"));
366 if (!is_integer($flag)) {
367 throw new AOOSException($this->core(), $this->tr("not_integer"));
370 $this->_flags[$column] = $flag;
371 return true;
374 public function getFlags($column) {
375 if (!$this->inColumnIndex($column)) {
376 throw new AOOSException($this->core(), $this->tr("index_error"));
378 if (!in_array($column, array_keys($this->_flags))) {
379 return false;
381 return $this->_flags[$column];
384 /* ----- Default values ------ */
386 * Sets the $column to have the default value of $value. Throws an AOOSException if column name isn't valid.
387 * @param $column Column name
388 * @param $value Default value
389 * @return true
391 public function setDefaultValue($column, $value) {
392 if (!$this->inColumnIndex($column)) {
393 throw new AOOSExcpetion($this->core(), $this->tr("index_error"));
395 $this->_defaults[$column] = $value;
396 return true;
400 * Returns the default value of the given column. Throws an AOOSException if column name isn't valid.
401 * @param $column Column name.
402 * @return value or false if none is set
404 public function getDefaultValue($column) {
405 if (!$this->inColumnIndex($column)) {
406 throw new AOOSException($this->core(), $this->tr("index_error"));
408 if (!in_array($column, array_keys($this->_defaults))) {
409 return false;
411 return $this->_defaults[$column];
415 * Returns the array of default values. Note that if a column isn't given a default value, null is used per default.
416 * @return array
418 public function defaultValues() {
419 return $this->_defaults;
423 /* ----- Storage ----- */
425 * Sets the type of the database.
426 * This must be called before AOOSModel::setTable().
427 * @param $source A string containing the type. Implemented: mysql
428 * @return true
430 public function setSource($source) {
431 if (!is_string($source)) {
432 throw new AOOSException($this->core(), $this->tr("not_string"));
435 switch (strtolower($source)) {
436 case("mysql"):
437 require_once("lib/AOOSMysqlInterface.php");
438 $this->_storage = new AOOSMysqlInterface($this->core());
439 break;
440 default:
441 exit("Not valid database");
443 return true;
447 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
448 * @param $table The table name. Must be a string.
449 * @return boolean
451 public function setTable($table) {
452 if (!is_object($this->_storage)) {
453 throw new AOOSException($this->core(), $this->tr("database_not_initialized"));
455 if (!is_string($table)) {
456 throw new AOOSException($this->core(), $this->tr("not_string"));
458 $table = $this->core()->getSetting("DBPrefix").$table;
459 $this->_table = $table;
461 return $this->_storage->setTable($table);
464 public function table() {
465 return $this->_table;
468 public function populate($where = null, $order = null, $limit = null) {
469 switch($this->_cache) {
470 case(AOOSMODEL_CACHE_STATIC):
471 if ($this->rows() != 0) {
472 return false;
474 break;
475 case(AOOSMODEL_CACHE_DYNAMIC):
476 $this->resetData();
477 break;
479 $fields = array();
480 if (is_array($where)) {
481 $where = new AOOSModelRow($this, $where);
482 $where = $where->toDatabaseArray();
484 elseif ($where instanceof AOOSModelRow) {
485 $where = $where->toDatabaseArray();
487 elseif ($where !== null) {
488 throw new AOOSException($this->core(), $this->tr("wrong_type"));
491 foreach ($this->_flags as $column => $flag) {
492 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
493 $fields[] = $column;
497 try {
498 $rows = $this->_storage->select($fields, $where, $order, $limit);
499 } catch (AOOSException $e) {
500 throw $e;
502 foreach ($rows as $row) {
503 $r = new AOOSModelRow($this);
504 $r->fromArray($row);
505 $this->_data[] = $r;
506 $this->_rows++;
508 return true;
511 public function create() {
512 $flags = array();
513 foreach ($this->_flags as $column => $flag) {
514 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
515 $flags[$column] = $flag;
518 $types = array_intersect_key($this->_types, $flags);
519 return $this->_storage->create($types, $flags);
522 public function drop() {
523 return $this->_storage->drop();
526 public function save() {
527 foreach ($this->data() as $row) {
528 switch ($row->flag()) {
529 case(AOOSMODELROW_PRIVATE_INSERT):
530 $this->_storage->insert($row->toDatabaseArray());
531 break;
532 case(AOOSMODELROW_PRIVATE_UPDATE):
533 $this->_storage->update($row->toDatabaseArray(), $row->old(), array(0,1));
534 break;
535 case(AOOSMODELROW_PRIVATE_REMOVE):
536 $this->_storage->remove($row->toDatabaseArray(), array(0,1));
537 break;
539 $row->setFlag(0);
541 return true;
545 class AOOSModelRow extends AOOSModule implements ArrayAccess{
546 private $_data = array();
547 private $_flag = 0;
548 private $_old = null;
550 public function __construct($parent, $value = null, $flag = 0) {
551 parent::__construct($parent);
552 $this->setFlag($flag);
553 $this->_data = $this->parent()->defaultValues();
554 if (is_array($value)) {
555 $values = array_intersect_key($value, $this->_data);
556 foreach ($values as $index => $val) {
557 $this->_setData($index, $value[$index]);
562 public function __set($name, $value) {
563 try {
564 $ret = $this->_setData($name, $value);
565 } catch (AOOSException $e) {
566 throw $e;
568 return $ret;
571 public function __get($name) {
572 if (!$this->parent()->inColumnIndex($name)) {
573 throw new AOOSException($this->core(), $this->tr("index_error"));
575 return $this->_data[$name];
578 public function __toString() {
579 return print_r($this->_data, true);
582 public function offsetSet($name, $value) {
583 return $this->$name = $value;
586 public function offsetGet($name) {
587 return $this->$name;
590 public function offsetExists($name) {
591 return $this->parent()->inColumnIndex($name);
594 public function offsetUnset($name) {
595 if (!$this->parent()->inColumnIndex($name)) {
596 throw new AOOSException($this->core(), $this->tr("index_error"));
598 $this->$name = null;
599 return true;
602 public function match($match) {
603 $matches = array_intersect_key($match, $this->_data);
604 foreach ($matches as $key => $val) {
605 if (!preg_match("/".$val."/", $this->$key)) {
606 return false;
609 return true;
612 public function toArray() {
613 $data = $this->_data;
614 return $data;
617 public function toDatabaseArray($data = null) {
618 if ($data == null) {
619 $data = $this->_data;
621 foreach ($data as $key => $value) {
622 $flags =$this->parent()->getFlags($key);
623 if (!($flags & AOOSMODEL_FLAG_FROM_DATABASE) || ($flags & AOOSMODEL_FLAG_PRIMARY_KEY)) {
624 unset($data[$key]);
625 continue;
627 $type = $this->parent()->getType($key);
628 if ($type == AOOSMODEL_TYPE_STRING || $type == AOOSMODEL_TYPE_TEXT) {
629 $data[$key] = "'".$value."'";
632 return $data;
636 public function fromArray($value) {
637 $this->_data = $this->parent()->defaultValues();
638 $values = array_intersect_key($value, $this->_data);
639 foreach ($values as $key => $value) {
640 $this->_data[$key] = $value;
642 return true;
645 public function old($db = true) {
646 return $db ? $this->toDatabaseArray($this->_old) : $this->_old;
649 public function save() {
650 return $this->parent()->save();
653 public function setFlag($flag) {
654 if (!is_integer($flag)) {
655 throw new AOOSException($this->core(), $this->tr("not_integer"));
657 $this->_flag = $flag;
658 return true;
661 public function flag() {
662 return $this->_flag;
665 /* ------ Private functions ----- */
666 private function _checkType($name, $value) {
667 $type = $this->parent()->getType($name);
668 switch($type) {
669 case(AOOSMODEL_TYPE_STRING):
670 case(AOOSMODEL_TYPE_TEXT):
671 return is_string($value);
672 break;
673 case(AOOSMODEL_TYPE_INTEGER):
674 $value = (int) $value;
675 return is_integer($value);
676 break;
677 case(AOOSMODEL_TYPE_BOOLEAN):
678 return is_bool($value);
679 break;
680 case(AOOSMODEL_TYPE_UNKNOWN):
681 return true;
682 break;
683 default:
684 return false;
685 break;
689 private function _checkFlags($name, $value) {
690 $flags = $this->parent()->getFlags($name);
691 if ($flags === false) {
692 return true;
694 if ($flags & AOOSMODEL_FLAG_UNIQUE) {
695 $column = $this->parent()->getColumn($name);
696 if (in_array($value, $column)) {
697 return false;
700 return true;
703 private function _doProperties($name, $value) {
704 $properties = $this->parent()->getProperties($name);
705 if ($properties === false) {
706 return $value;
708 if ($properties & AOOSMODEL_PROP_TIME) {
709 $value = time();
710 return $value;
712 if ($properties & AOOSMODEL_PROP_HASH) {
713 $value = hash("sha256", $value);
715 if ($properties & AOOSMODEL_PROP_NOHTML) {
716 $value = htmlspecialchars($value);
718 if ($properties & AOOSMODEL_PROP_ESCAPE) {
719 $value = mysql_real_escape_string($value);
721 if ($properties & AOOSMODEL_PROP_STRIP) {
722 $value = rtrim(trim($value));
724 return $value;
727 private function _doConstraints($name, $value) {
728 $constraints = $this->parent()->getConstraints($name);
729 if ($constraints === false) {
730 return $value;
732 foreach ($constraints as $constraint) {
733 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
734 continue;
736 $value = call_user_func(array($constraint[0], "execute"), $constraint[1], $value);
737 if (false === $value) {
738 throw new AOOSException($this->core(), $this->tr("constraint_fail"), $name);
739 return false;
742 return $value;
745 private function _setData($name, $value) {
746 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE) {
747 return false;
749 if (!$this->parent()->inColumnIndex($name)) {
750 throw new AOOSException($this->core(), $this->tr("index_error"));
752 if (!$this->_checkType($name, $value)) {
753 throw new AOOSException($this->core(), $this->tr("wrong_type"), $name);
755 try {
756 $value = $this->_doProperties($name, $value);
757 $value = $this->_doConstraints($name, $value);
758 } catch (AOOSException $e) {
759 throw $e;
761 if (!$this->_checkFlags($name, $value)) {
762 throw new AOOSException($this->core(), $this->tr("flags_failed"), $name);
764 if ($this->flag() == 0) {
765 $this->setFlag(AOOSMODELROW_PRIVATE_UPDATE);
766 $this->_old = $this->_data;
768 $this->_data[$name] = $value;
769 return true;