Typo
[AOOS.git] / AOOSModel.php
blobe05f4e09e02a584a7de3cd45f6c020e53bc38414
1 <?php
2 AOOSCore::register("AOOSMysqlInterface", "lib/AOOSMysqlInterface.php");
3 require_once("lib/AOOSModelConstraints.php");
5 define('AOOSMODEL_TYPE_STRING', 1);
6 define('AOOSMODEL_TYPE_TEXT', 2);
7 define('AOOSMODEL_TYPE_INTEGER', 3);
8 define('AOOSMODEL_TYPE_BOOLEAN', 4);
9 define('AOOSMODEL_TYPE_UNKNOWN', 5);
11 define('AOOSMODEL_PROP_HASH', 1);
12 define('AOOSMODEL_PROP_NOHTML', 2);
13 define('AOOSMODEL_PROP_STRIP', 4);
14 define('AOOSMODEL_PROP_ESCAPE', 8);
15 define('AOOSMODEL_PROP_TIME', 16);
17 define('AOOSMODEL_FLAG_FROM_DATABASE', 1);
18 define('AOOSMODEL_FLAG_UNIQUE', 2);
19 define('AOOSMODEL_FLAG_PRIMARY_KEY', 4);
20 define('AOOSMODEL_FLAG_GUI_PRIVATE', 8);
21 define('AOOSMODEL_FLAG_GUI_NOTEDITABLE',16);
23 define('AOOSMODELROW_PRIVATE_INSERT', 1);
24 define('AOOSMODELROW_PRIVATE_UPDATE', 2);
25 define('AOOSMODELROW_PRIVATE_REMOVE', 3);
27 define('AOOSMODEL_CACHE_STATIC', 1); // Only populate if empty
28 define('AOOSMODEL_CACHE_DYNAMIC', 2); // Clear and populate always
30 class AOOSModel extends AOOSModule {
31 private $_data = array();
32 private $_columnIndex = array();
33 private $_properties = array();
34 private $_constraints = array();
35 private $_types = array();
36 private $_flags = array();
37 private $_defaults = array();
38 private $_storage = null;
39 private $_rows = 0;
40 private $_lastMatch = 0;
41 private $_cache = AOOSMODEL_CACHE_DYNAMIC;
43 private $_table = null;
45 /* ------ Data functions ------ */
46 /**
47 * Appends a row to the end of the model. If given an array, the function creates the row with the values specified
48 * in the array. Otherwise the row is empty.
49 * @param $values An array with the values to fill the row with. Array keys are column indeces and array values are
50 * the values used to fill the array.
51 * @return true
53 public function appendRow($values = null) {
54 if ($values instanceof AOOSModelRow) {
55 $values = $values->toArray();
57 $row = new AOOSModelRow($this, $values, AOOSMODELROW_PRIVATE_INSERT);
58 $this->_data[] = $row;
59 $this->_rows++;
60 return true;
63 /**
64 * Sets value of $column in row $row to be $value. Throws an AOOSException if $row isn't valid, if the specified
65 * column isn't a valid column index or setting the value for some reason failed.
66 * @param $row An integer representing the row
67 * @param $column A valid column index.
68 * @param $value The value
69 * @return boolean
71 public function setData($row, $column, $value) {
72 try {
73 $r = $this->getRow($row);
74 $ret = $r->$column = $value;
75 } catch (AOOSException $e) {
76 throw $e;
78 return $ret;
81 /**
82 * Returns all rows that haven't been removed
83 * @return array
85 public function data() {
86 $rows = array();
87 foreach ($this->_data as $row) {
88 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE) {
89 $rows[] = $row;
92 return $rows;
95 /**
96 * Returns the row at $index position. If a negative value is given it is counted from the end. Throws an AOOSException
97 * if $index isn't within bounds.
98 * @param $index An integer representing the row
99 * @sa getRows
100 * @return AOOSModelRow
102 public function getRow($index) {
103 if ($index < 0) {
104 $index = $this->rows()+$index;
106 if($index >= $this->rows() || $index < 0) {
107 throw new AOOSException($this->core(), $this->tr("bound_error"), $index);
109 return $this->_data[$index];
113 * 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.
114 * @param $start The start row. An integer.
115 * @param $length Get $length number of rows. An integer.
116 * @sa getRows
117 * @return array
119 public function getRows($start, $length) {
120 $rows = array();
121 if ($start < 0) {
122 $start = $this->rows()+$start;
124 elseif ($start > $this->rows() || $start+$length > $this->rows()) {
125 throw new AOOSException($this->core(), $this->tr("bound_error"));
127 for ($i=0; $i<$length; ++$i) {
128 $rows[$start] = $this->_data[$start];
129 $start++;
131 return $rows;
135 * Returns the number of rows.
136 * @return integer
138 public function rows() {
139 return $this->_rows;
143 * Removes rows that match $where. $limit defines the number of rows to remove. Both $where and $limit are arrays.
144 * $limit consists of two entries, $limit[0] which defines the start of which rows to remove and $limit[1] which
145 * defines the number of rows to remove. Throws an AOOSException if $where or $limit aren't arrays.
146 * @param $where An array against which all rows are matched.
147 * @param $limit The limit array.
148 * @return true.
150 public function remove($where, $limit) {
151 if (!is_array($where) || !is_array($limit)) {
152 throw new AOOSException($this->core(), $this->tr("not_array"));
154 $rows = $this->findAll($where);
155 $rows = array_slice($rows, $limit[0], $limit[1]);
156 foreach ($rows as $row) {
157 $row->setFlag(AOOSMODELROW_PRIVATE_REMOVE);
159 return true;
163 * Returns the column $column. $start defines the start row and $length defines the number of rows. $start and
164 * $length are optional. Throws an AOOSException if $column isn't a valid column index.
165 * @param $column The column name.
166 * @param $start An integer representing the start row. Defaults to 0.
167 * @param $length The number or rows to use. Defaults to all rows.
168 * @return array
170 public function getColumn($column, $start = 0, $length = null) {
171 $columnA = array();
172 if (!$this->inColumnIndex($column)) {
173 throw new AOOSException($this->core(), $this->tr("index_error"));
175 if ($length === null) {
176 $length = $this->rows();
178 $rows = $this->getRows($start, $length);
179 foreach ($rows as $index => $row) {
180 $columnA[$index] = $row[$column];
182 return $columnA;
186 * Returns the first row that matches $match from $offset and forwards. The rows are matched using preg_match and
187 * delimiters should NOT be included in the array values. Throws an AOOSException if $offset isn't within bounds.
188 * @param $match An array in which keys are column names and values are the values to match against.
189 * @param $offset The row to start searching in.
190 * @sa findAll
191 * @return AOOSModelRow or false if no rows are found.
193 public function find($match, $offset = 0) {
194 $m = false;
195 for($i = $offset; $i < $this->rows(); ++$i) {
196 try {
197 $row = $this->getRow($i);
198 if ($row->match($match) !== false) {
199 $m = $row;
200 $this->_lastMatch = $i;
201 break;
203 } catch (AOOSException $e) {
204 throw $e;
207 return $m;
211 * Returns all rows that match $match from $offset and forwards. Throws an AOOSException if $offset isn't within
212 * bounds.
213 * @param $match An array in which keys are column names and values are the values to match against.
214 * @param $offset The row to start searching in.
215 * @sa find
216 * @return array
218 public function findAll($match, $offset = 0) {
219 $m = array();
220 $i = 0;
221 while (true) {
222 try {
223 $row = $this->find($match, $i);
224 } catch (AOOSException $e) {
225 throw $e;
227 if ($row === false) {
228 break;
230 $m[] = $row;
231 $i = $this->_lastMatch+1;
233 return $m;
237 * Empties the model and removes all propeties, constraints and flags. Only column indeces are kept.
238 * @sa resetData
239 * @return true
241 public function reset() {
242 $this->_data = array();
243 $this->_properties = array();
244 $this->_constraints = array();
245 $this->_flags = array();
246 $this->_defaults = array_fill_keys($this->columnIndex(), null);
247 $this->_rows = 0;
248 return true;
252 * Empties the model.
253 * @sa reset
254 * @return true;
256 public function resetData() {
257 $this->_data = array();
258 $this->_rows = 0;
259 return true;
262 /* ----- Cache mode ------ */
263 public function setCacheMode($cacheMode) {
264 if ($cacheMode != AOOSMODEL_CACHE_STATIC && $cacheMode != AOOSMODEL_CACHE_DYNAMIC) {
265 throw new AOOSExcpetion($this->core(), $this->tr("not_valid_cache_mode"));
267 $this->_cache = $cacheMode;
268 return true;
271 public function cacheMode() {
272 return $this->_cache;
276 /* ----- Column index ------ */
277 public function setColumnIndex($columnindex) {
278 if (!is_array($columnindex)) {
279 throw new AOOSException($this->core(), $this->tr("not_array"));
281 $this->_columnIndex = $columnindex;
282 $this->reset();
283 return true;
286 public function columnIndex() {
287 return $this->_columnIndex;
290 public function inColumnIndex($column) {
291 return in_array($column, $this->columnIndex());
295 /* ------ Properties, types, flags and constraints ------ */
296 public function setProperties($column, $type, $flag = 0, $properties = 0) {
297 if (!$this->inColumnIndex($column)) {
298 throw new AOOSException($this->core(), $this->tr("index_error"));
300 if (!is_integer($properties) || !is_integer($type) || !is_integer($flag)) {
301 throw new AOOSException($this->core(), $this->tr("not_integer"));
303 $this->_properties[$column] = $properties;
304 $this->_types[$column] = $type;
305 $this->_flags[$column] = $flag;
306 return null;
309 public function getProperties($column) {
310 if (!$this->inColumnIndex($column)) {
311 throw new AOOSException($this->core(), $this->tr("index_error"));
313 if (!in_array($column, array_keys($this->_properties))) {
314 return false;
316 return $this->_properties[$column];
319 public function addConstraint($column, $constraint, $args) { // XXX Variable argument count
320 if (!$this->inColumnIndex($column)) {
321 throw new AOOSException($this->core(), $this->tr("index_error"));
323 $name = $constraint."Constraint";
324 if (!class_exists($name)) {
325 return false;
327 if (!is_array($args)) {
328 $args = array($args);
330 $numArgs = call_user_func(array($name, "numArgs"));
331 if (count($args) != $numArgs) {
332 return false;
334 if (!in_array($column,array_keys($this->_constraints))) {
335 $this->_constraints[$column] = array();
337 $this->_constraints[$column][] = array($name, $args);
338 return true;
341 public function getConstraints($column) {
342 if (!$this->inColumnIndex($column)) {
343 throw new AOOSException($this->core(), $this->tr("index_error"));
345 if (!in_array($column, array_keys($this->_constraints))) {
346 return false;
348 return $this->_constraints[$column];
352 * Returns the type of $column.
353 * @param Column name.
354 * @return integer
356 public function getType($column) {
357 if (!$this->inColumnIndex($column)) {
358 throw new AOOSException($this->core(), $this->tr("index_error"));
360 return $this->_types[$column];
363 public function setFlags($column, $flag) {
364 if (!$this->inColumnIndex($column)) {
365 throw new AOOSException($this->core(), $this->tr("index_error"));
367 if (!is_integer($flag)) {
368 throw new AOOSException($this->core(), $this->tr("not_integer"));
371 $this->_flags[$column] = $flag;
372 return true;
375 public function getFlags($column) {
376 if (!$this->inColumnIndex($column)) {
377 throw new AOOSException($this->core(), $this->tr("index_error"));
379 if (!in_array($column, array_keys($this->_flags))) {
380 return false;
382 return $this->_flags[$column];
385 /* ----- Default values ------ */
387 * Sets the $column to have the default value of $value. Throws an AOOSException if column name isn't valid.
388 * @param $column Column name
389 * @param $value Default value
390 * @return true
392 public function setDefaultValue($column, $value) {
393 if (!$this->inColumnIndex($column)) {
394 throw new AOOSExcpetion($this->core(), $this->tr("index_error"));
396 $this->_defaults[$column] = $value;
397 return true;
401 * Returns the default value of the given column. Throws an AOOSException if column name isn't valid.
402 * @param $column Column name.
403 * @return value or false if none is set
405 public function getDefaultValue($column) {
406 if (!$this->inColumnIndex($column)) {
407 throw new AOOSException($this->core(), $this->tr("index_error"));
409 if (!in_array($column, array_keys($this->_defaults))) {
410 return false;
412 return $this->_defaults[$column];
416 * Returns the array of default values. Note that if a column isn't given a default value, null is used per default.
417 * @return array
419 public function defaultValues() {
420 return $this->_defaults;
424 /* ----- Storage ----- */
426 * Sets the type of the database.
427 * This must be called before AOOSModel::setTable().
428 * @param $source A string containing the type. Implemented: mysql
429 * @return true
431 public function setSource($source) {
432 if (!is_string($source)) {
433 throw new AOOSException($this->core(), $this->tr("not_string"));
436 switch (strtolower($source)) {
437 case("mysql"):
438 require_once("lib/AOOSMysqlInterface.php");
439 $this->_storage = new AOOSMysqlInterface($this->core());
440 break;
441 default:
442 exit("Not valid database");
444 return true;
448 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
449 * @param $table The table name. Must be a string.
450 * @return boolean
452 public function setTable($table) {
453 if (!is_object($this->_storage)) {
454 throw new AOOSException($this->core(), $this->tr("database_not_initialized"));
456 if (!is_string($table)) {
457 throw new AOOSException($this->core(), $this->tr("not_string"));
459 $table = $this->core()->getSetting("DBPrefix").$table;
460 $this->_table = $table;
462 return $this->_storage->setTable($table);
465 public function table() {
466 return $this->_table;
469 public function populate($where = null, $order = null, $limit = null) {
470 switch($this->_cache) {
471 case(AOOSMODEL_CACHE_STATIC):
472 if ($this->rows() != 0) {
473 return false;
475 break;
476 case(AOOSMODEL_CACHE_DYNAMIC):
477 $this->resetData();
478 break;
480 $fields = array();
481 if (is_array($where)) {
482 $where = new AOOSModelRow($this, $where);
483 $where = $where->toDatabaseArray();
485 elseif ($where instanceof AOOSModelRow) {
486 $where = $where->toDatabaseArray();
488 elseif ($where !== null) {
489 throw new AOOSException($this->core(), $this->tr("wrong_type"));
492 foreach ($this->_flags as $column => $flag) {
493 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
494 $fields[] = $column;
498 try {
499 $rows = $this->_storage->select($fields, $where, $order, $limit);
500 } catch (AOOSException $e) {
501 throw $e;
503 foreach ($rows as $row) {
504 $r = new AOOSModelRow($this);
505 $r->fromArray($row);
506 $this->_data[] = $r;
507 $this->_rows++;
509 return true;
512 public function create() {
513 $flags = array();
514 foreach ($this->_flags as $column => $flag) {
515 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
516 $flags[$column] = $flag;
519 $types = array_intersect_key($this->_types, $flags);
520 return $this->_storage->create($types, $flags);
523 public function drop() {
524 return $this->_storage->drop();
527 public function save() {
528 foreach ($this->data() as $row) {
529 switch ($row->flag()) {
530 case(AOOSMODELROW_PRIVATE_INSERT):
531 $this->_storage->insert($row->toDatabaseArray());
532 break;
533 case(AOOSMODELROW_PRIVATE_UPDATE):
534 $this->_storage->update($row->toDatabaseArray(), $row->old(), array(0,1));
535 break;
536 case(AOOSMODELROW_PRIVATE_REMOVE):
537 $this->_storage->remove($row->toDatabaseArray(), array(0,1));
538 break;
540 $row->setFlag(0);
542 return true;
546 class AOOSModelRow extends AOOSModule implements ArrayAccess{
547 private $_data = array();
548 private $_flag = 0;
549 private $_old = null;
551 public function __construct($parent, $value = null, $flag = 0) {
552 parent::__construct($parent);
553 $this->setFlag($flag);
554 $this->_data = $this->parent()->defaultValues();
555 if (is_array($value)) {
556 $values = array_intersect_key($value, $this->_data);
557 foreach ($values as $index => $val) {
558 $this->_setData($index, $value[$index]);
563 public function __set($name, $value) {
564 try {
565 $ret = $this->_setData($name, $value);
566 } catch (AOOSException $e) {
567 throw $e;
569 return $ret;
572 public function __get($name) {
573 if (!$this->parent()->inColumnIndex($name)) {
574 throw new AOOSException($this->core(), $this->tr("index_error"));
576 return $this->_data[$name];
579 public function __toString() {
580 return print_r($this->_data, true);
583 public function offsetSet($name, $value) {
584 return $this->$name = $value;
587 public function offsetGet($name) {
588 return $this->$name;
591 public function offsetExists($name) {
592 return $this->parent()->inColumnIndex($name);
595 public function offsetUnset($name) {
596 if (!$this->parent()->inColumnIndex($name)) {
597 throw new AOOSException($this->core(), $this->tr("index_error"));
599 $this->$name = null;
600 return true;
603 public function match($match) {
604 $matches = array_intersect_key($match, $this->_data);
605 foreach ($matches as $key => $val) {
606 if (!preg_match("/".$val."/", $this->$key)) {
607 return false;
610 return true;
613 public function toArray() {
614 $data = $this->_data;
615 return $data;
618 public function toDatabaseArray($data = null) {
619 if ($data == null) {
620 $data = $this->_data;
622 foreach ($data as $key => $value) {
623 $flags =$this->parent()->getFlags($key);
624 if (!($flags & AOOSMODEL_FLAG_FROM_DATABASE) || ($flags & AOOSMODEL_FLAG_PRIMARY_KEY) || $this->_is_empty($key, $value)) {
625 unset($data[$key]);
626 continue;
628 $type = $this->parent()->getType($key);
629 if ($type == AOOSMODEL_TYPE_STRING || $type == AOOSMODEL_TYPE_TEXT) {
630 $data[$key] = "'".$value."'";
631 } elseif ($type == AOOSMODEL_TYPE_BOOLEAN) {
632 $data[$key] = $value ? 1 : 0;
635 return $data;
639 public function fromArray($value) {
640 $this->_data = $this->parent()->defaultValues();
641 $values = array_intersect_key($value, $this->_data);
642 foreach ($values as $key => $value) {
643 $this->_data[$key] = $value;
645 return true;
648 public function old($db = true) {
649 return $db ? $this->toDatabaseArray($this->_old) : $this->_old;
652 public function save() {
653 return $this->parent()->save();
656 public function setFlag($flag) {
657 if (!is_integer($flag)) {
658 throw new AOOSException($this->core(), $this->tr("not_integer"));
660 $this->_flag = $flag;
661 return true;
664 public function flag() {
665 return $this->_flag;
668 /* ------ Private functions ----- */
669 private function _checkType($name, $value) {
670 $type = $this->parent()->getType($name);
671 switch($type) {
672 case(AOOSMODEL_TYPE_STRING):
673 case(AOOSMODEL_TYPE_TEXT):
674 return is_string($value);
675 break;
676 case(AOOSMODEL_TYPE_INTEGER):
677 $value = (int) $value;
678 return is_integer($value);
679 break;
680 case(AOOSMODEL_TYPE_BOOLEAN):
681 return is_bool($value);
682 break;
683 case(AOOSMODEL_TYPE_UNKNOWN):
684 return true;
685 break;
686 default:
687 return false;
688 break;
692 private function _checkFlags($name, $value) {
693 $flags = $this->parent()->getFlags($name);
694 if ($flags === false) {
695 return true;
697 if ($flags & AOOSMODEL_FLAG_UNIQUE) {
698 $column = $this->parent()->getColumn($name);
699 if (in_array($value, $column)) {
700 return false;
703 return true;
706 private function _doProperties($name, $value) {
707 $properties = $this->parent()->getProperties($name);
708 if ($properties === false) {
709 return $value;
711 if ($properties & AOOSMODEL_PROP_TIME) {
712 $value = time();
713 return $value;
715 if ($properties & AOOSMODEL_PROP_HASH) {
716 $value = hash("sha256", $value);
718 if ($properties & AOOSMODEL_PROP_NOHTML) {
719 $value = htmlspecialchars($value);
721 if ($properties & AOOSMODEL_PROP_ESCAPE) {
722 $value = mysql_real_escape_string($value);
724 if ($properties & AOOSMODEL_PROP_STRIP) {
725 $value = rtrim(trim($value));
727 return $value;
730 private function _doConstraints($name, $value) {
731 $constraints = $this->parent()->getConstraints($name);
732 if ($constraints === false) {
733 return $value;
735 foreach ($constraints as $constraint) {
736 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
737 continue;
739 $value = call_user_func(array($constraint[0], "execute"), $constraint[1], $value);
740 if (false === $value) {
741 throw new AOOSException($this->core(), $this->tr("constraint_fail"), $name);
742 return false;
745 return $value;
748 private function _setData($name, $value) {
749 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE) {
750 return false;
752 if (!$this->parent()->inColumnIndex($name)) {
753 throw new AOOSException($this->core(), $this->tr("index_error"));
755 if (!$this->_checkType($name, $value)) {
756 throw new AOOSException($this->core(), $this->tr("wrong_type"), $name);
758 try {
759 $value = $this->_doProperties($name, $value);
760 $value = $this->_doConstraints($name, $value);
761 } catch (AOOSException $e) {
762 throw $e;
764 if (!$this->_checkFlags($name, $value)) {
765 throw new AOOSException($this->core(), $this->tr("flags_failed"), $name);
767 if ($this->flag() == 0) {
768 $this->setFlag(AOOSMODELROW_PRIVATE_UPDATE);
769 $this->_old = $this->_data;
771 $this->_data[$name] = $value;
772 return true;
775 private function _is_empty($key, $value) {
776 $t = $this->parent()->getType($key);
777 switch($t) {
778 case(AOOSMODEL_TYPE_STRING):
779 case(AOOSMODEL_TYPE_TEXT):
780 return $value == "";
781 break;
782 case(AOOSMODEL_TYPE_INTEGER):
783 return !is_integer($value);
784 break;
785 default:
786 return false;
787 break;