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;
40 private $_lastMatch = 0;
41 private $_cache = AOOSMODEL_CACHE_DYNAMIC
;
43 private $_table = null;
45 /* ------ Data functions ------ */
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.
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;
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
71 public function setData($row, $column, $value) {
73 $r = $this->getRow($row);
74 $ret = $r->$column = $value;
75 } catch (AOOSException
$e) {
82 * Returns all rows that haven't been removed
85 public function data() {
87 foreach ($this->_data
as $row) {
88 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE
) {
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
100 * @return AOOSModelRow
102 public function getRow($index) {
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.
119 public function getRows($start, $length) {
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];
135 * Returns the number of rows.
138 public function 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.
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
);
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.
170 public function getColumn($column, $start = 0, $length = null) {
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];
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.
191 * @return AOOSModelRow or false if no rows are found.
193 public function find($match, $offset = 0) {
195 for($i = $offset; $i < $this->rows(); ++
$i) {
197 $row = $this->getRow($i);
198 if ($row->match($match) !== false) {
200 $this->_lastMatch
= $i;
203 } catch (AOOSException
$e) {
211 * Returns all rows that match $match from $offset and forwards. Throws an AOOSException if $offset isn't within
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.
218 public function findAll($match, $offset = 0) {
223 $row = $this->find($match, $i);
224 } catch (AOOSException
$e) {
227 if ($row === false) {
231 $i = $this->_lastMatch+
1;
237 * Empties the model and removes all propeties, constraints and flags. Only column indeces are kept.
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);
256 public function resetData() {
257 $this->_data
= array();
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;
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;
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;
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
))) {
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)) {
327 if (!is_array($args)) {
328 $args = array($args);
330 $numArgs = call_user_func(array($name, "numArgs"));
331 if (count($args) != $numArgs) {
334 if (!in_array($column,array_keys($this->_constraints
))) {
335 $this->_constraints
[$column] = array();
337 $this->_constraints
[$column][] = array($name, $args);
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
))) {
348 return $this->_constraints
[$column];
352 * Returns the type of $column.
353 * @param Column name.
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;
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
))) {
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
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;
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
))) {
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.
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
431 public function setSource($source) {
432 if (!is_string($source)) {
433 throw new AOOSException($this->core(), $this->tr("not_string"));
436 switch (strtolower($source)) {
438 require_once("lib/AOOSMysqlInterface.php");
439 $this->_storage
= new AOOSMysqlInterface($this->core());
442 exit("Not valid database");
448 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
449 * @param $table The table name. Must be a string.
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) {
476 case(AOOSMODEL_CACHE_DYNAMIC
):
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
) {
499 $rows = $this->_storage
->select($fields, $where, $order, $limit);
500 } catch (AOOSException
$e) {
503 foreach ($rows as $row) {
504 $r = new AOOSModelRow($this);
512 public function create() {
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());
533 case(AOOSMODELROW_PRIVATE_UPDATE
):
534 $this->_storage
->update($row->toDatabaseArray(), $row->old(), array(0,1));
536 case(AOOSMODELROW_PRIVATE_REMOVE
):
537 $this->_storage
->remove($row->toDatabaseArray(), array(0,1));
546 class AOOSModelRow
extends AOOSModule
implements ArrayAccess
{
547 private $_data = array();
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) {
565 $ret = $this->_setData($name, $value);
566 } catch (AOOSException
$e) {
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) {
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"));
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)) {
613 public function toArray() {
614 $data = $this->_data
;
618 public function toDatabaseArray($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)) {
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;
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;
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;
664 public function flag() {
668 /* ------ Private functions ----- */
669 private function _checkType($name, $value) {
670 $type = $this->parent()->getType($name);
672 case(AOOSMODEL_TYPE_STRING
):
673 case(AOOSMODEL_TYPE_TEXT
):
674 return is_string($value);
676 case(AOOSMODEL_TYPE_INTEGER
):
677 $value = (int) $value;
678 return is_integer($value);
680 case(AOOSMODEL_TYPE_BOOLEAN
):
681 return is_bool($value);
683 case(AOOSMODEL_TYPE_UNKNOWN
):
692 private function _checkFlags($name, $value) {
693 $flags = $this->parent()->getFlags($name);
694 if ($flags === false) {
697 if ($flags & AOOSMODEL_FLAG_UNIQUE
) {
698 $column = $this->parent()->getColumn($name);
699 if (in_array($value, $column)) {
706 private function _doProperties($name, $value) {
707 $properties = $this->parent()->getProperties($name);
708 if ($properties === false) {
711 if ($properties & AOOSMODEL_PROP_TIME
) {
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));
730 private function _doConstraints($name, $value) {
731 $constraints = $this->parent()->getConstraints($name);
732 if ($constraints === false) {
735 foreach ($constraints as $constraint) {
736 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
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);
748 private function _setData($name, $value) {
749 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE
) {
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);
759 $value = $this->_doProperties($name, $value);
760 $value = $this->_doConstraints($name, $value);
761 } catch (AOOSException
$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;
775 private function _is_empty($key, $value) {
776 $t = $this->parent()->getType($key);
778 case(AOOSMODEL_TYPE_STRING
):
779 case(AOOSMODEL_TYPE_TEXT
):
782 case(AOOSMODEL_TYPE_INTEGER
):
783 return !is_integer($value);