AOOSModel: Major refactor
[AOOS.git] / AOOSModel.php
bloba30f874bd83ba8a13fb88683d1e42d1f0cb45598
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);
15 define('AOOSMODEL_FLAG_FROM_DATABASE', 1);
16 define('AOOSMODEL_FLAG_UNIQUE', 2);
17 define('AOOSMODEL_FLAG_PRIMARY_KEY', 4);
19 define('AOOSMODELROW_PRIVATE_INSERT', 1);
20 define('AOOSMODELROW_PRIVATE_UPDATE', 2);
21 define('AOOSMODELROW_PRIVATE_REMOVE', 3);
23 class AOOSModel extends AOOSModule {
24 private $_data = array();
25 private $_columnIndex = array();
26 private $_properties = array();
27 private $_constraints = array();
28 private $_types = array();
29 private $_flags = array();
30 private $_storage = null;
31 private $_rows = 0;
32 private $_lastMatch = 0;
34 /* ------ Data functions ------ */
35 public function appendRow($values = null) {
36 $row = new AOOSModelRow($this, $values, AOOSMODELROW_PRIVATE_INSERT);
37 $this->_data[] = $row;
38 $this->_rows++;
39 return null;
42 public function setData($row, $column, $value) {
43 try {
44 $r = $this->getRow($row);
45 } catch (AOOSException $e) {
46 throw $e;
48 if (!$this->inColumnIndex($column)) {
49 throw new AOOSException($this->core(), $this->tr("index_error"));
51 return $r->$column = $value;
54 public function data() {
55 $rows = array();
56 foreach ($this->_data as $row) {
57 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE) {
58 $rows[] = $row;
61 return $rows;
64 public function getRow($index) {
65 if ($index < 0) {
66 $index = $this->rows()+$index;
68 if($index >= $this->rows() || $index < 0) {
69 throw new AOOSException($this->core(), $this->tr("bound_error"), $index);
71 return $this->_data[$index];
74 public function getRows($start, $length) {
75 $rows = array();
76 if ($start < 0) {
77 $start = $this->rows()+$start;
79 elseif ($start > $this->rows() || $start+$length > $this->rows()) {
80 throw new AOOSException($this->core(), $this->tr("bound_error"));
82 for ($i=0; $i<$length; ++$i) {
83 $rows[$start] = $this->_data[$start];
84 $start++;
86 return $rows;
89 public function rows() {
90 return $this->_rows;
93 public function remove($where, $limit) {
94 if (!is_array($where) || !is_array($limit)) {
95 throw new AOOSException($this->core(), $this->tr("not_array"));
97 $rows = $this->findAll($where);
98 $rows = array_slice($rows, $limit[0], $limit[1]);
99 foreach ($rows as $row) {
100 $row->setFlag(AOOSMODELROW_PRIVATE_REMOVE);
102 return true;
105 public function getColumn($column, $start = 0, $length = null) {
106 $columnA = array();
107 if (!$this->inColumnIndex($column)) {
108 throw new AOOSException($this->core(), $this->tr("index_error"));
110 if ($length === null) {
111 $length = $this->rows();
113 $rows = $this->getRows($start, $length);
114 foreach ($rows as $index => $row) {
115 $columnA[$index] = $row[$column];
117 return $columnA;
120 public function find($match, $offset = 0) {
121 $m = false;
122 for($i = $offset; $i < $this->rows(); ++$i) {
123 try {
124 $row = $this->getRow($i);
125 if ($row->match($match) !== false) {
126 $m = $row;
127 $this->_lastMatch = $i;
128 break;
130 } catch (AOOSException $e) {
131 throw $e;
134 return $m;
137 public function findAll($match, $offset = 0) {
138 $m = array();
139 $i = 0;
140 while (true) {
141 $row = $this->find($match, $i);
142 if ($row === false) {
143 break;
145 $m[] = $row;
146 $i = $this->_lastMatch+1;
148 return $m;
151 public function reset() {
152 $this->_data = array();
153 $this->_properties = array();
154 $this->_constraints = array();
155 $this->_flags = array();
156 return true;
160 /* ----- Column index ------ */
161 public function setColumnIndex($columnindex) {
162 if (!is_array($columnindex)) {
163 throw new AOOSException($this->core(), $this->tr("not_array"));
165 $this->_columnIndex = $columnindex;
166 $this->reset();
167 return true;
170 public function columnIndex() {
171 return $this->_columnIndex;
174 public function inColumnIndex($column) {
175 return in_array($column, $this->columnIndex());
179 /* ------ Properties, types, flags and constraints ------ */
180 public function setProperties($column, $type, $properties = 0, $flag = 0) {
181 if (!$this->inColumnIndex($column)) {
182 throw new AOOSException($this->core(), $this->tr("index_error"));
184 if (!is_integer($properties) || !is_integer($type) || !is_integer($flag)) {
185 throw new AOOSException($this->core(), $this->tr("not_integer"));
188 $this->_properties[$column] = $properties;
189 $this->_types[$column] = $type;
190 $this->_flags[$column] = $flag;
191 return null;
194 public function getProperties($column) {
195 if (!$this->inColumnIndex($column)) {
196 throw new AOOSException($this->core(), $this->tr("index_error"));
198 if (!in_array($column, array_keys($this->_properties))) {
199 return false;
201 return $this->_properties[$column];
204 public function addConstraint($column, $constraint, $args) {
205 if (!$this->inColumnIndex($column)) {
206 throw new AOOSException($this->core(), $this->tr("index_error"));
208 $name = $constraint."Constraint";
209 if (!class_exists($name)) {
210 return false;
212 if (!is_array($args)) {
213 $args = array($args);
215 $numArgs = call_user_func(array($name, "numArgs"));
216 if (count($args) != $numArgs) {
217 return false;
219 if (!in_array($column,array_keys($this->_constraints))) {
220 $this->_constraints[$column] = array();
222 $this->_constraints[$column][] = array($name, $args);
223 return true;
226 public function getConstraints($column) {
227 if (!$this->inColumnIndex($column)) {
228 throw new AOOSException($this->core(), $this->tr("index_error"));
230 if (!in_array($column, array_keys($this->_constraints))) {
231 return false;
233 return $this->_constraints[$column];
237 * Returns the type of $column.
238 * @param Column name.
239 * @return integer
241 public function getType($column) {
242 if (!$this->inColumnIndex($column)) {
243 throw new AOOSException($this->core(), $this->tr("index_error"));
245 return $this->_types[$column];
248 public function setFlags($column, $flag) {
249 if (!$this->inColumnIndex($column)) {
250 throw new AOOSException($this->core(), $this->tr("index_error"));
252 if (!is_integer($flag)) {
253 throw new AOOSException($this->core(), $this->tr("not_integer"));
256 $this->_flags[$column] = $flag;
257 return true;
260 public function getFlags($column) {
261 if (!$this->inColumnIndex($column)) {
262 throw new AOOSException($this->core(), $this->tr("index_error"));
264 if (!in_array($column, array_keys($this->_flags))) {
265 return false;
267 return $this->_flags[$column];
271 /* ----- Storage ----- */
273 * Sets the type of the database.
274 * This must be called before AOOSModel::setTable().
275 * @param $source A string containing the type. Implemented: mysql
276 * @return true
278 public function setSource($source) {
279 if (!is_string($source)) {
280 throw new AOOSException($this->core(), $this->tr("not_string"));
283 switch (strtolower($source)) {
284 case("mysql"):
285 require_once("lib/AOOSMysqlInterface.php");
286 $this->_storage = new AOOSMysqlInterface($this->core());
287 break;
289 return true;
293 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
294 * @param $table The table name. Must be a string.
295 * @return boolean
297 public function setTable($table) {
298 if (!is_object($this->_storage)) {
299 throw new AOOSException($this->core(), $this->tr("database_not_initialized"));
301 if (!is_string($table)) {
302 throw new AOOSException($this->core(), $this->tr("not_string"));
304 $table = $this->core()->getSetting("DBPrefix").$table;
306 return $this->_storage->setTable($table);
309 public function populate($where = null, $limit = null, $order = null) {
310 $fields = array();
311 if (is_array($where)) {
312 $where = new AOOSModelRow($this, $where);
313 $where = $where->toDatabaseArray();
315 elseif ($where instanceof AOOSModelRow) {
316 $where = $where->toDatabaseArray();
318 elseif ($where !== null) {
319 throw new AOOSException($this->core(), $this->tr("wrong_type"));
322 foreach ($this->_flags as $column => $flag) {
323 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
324 $fields[] = $column;
328 try {
329 $rows = $this->_storage->select($fields, $where, $limit, $order);
330 } catch (AOOSException $e) {
331 throw $e;
333 foreach ($rows as $row) {
334 $r = new AOOSModelRow($this);
335 $r->fromArray($row);
336 $this->_data[] = $r;
337 $this->_rows++;
339 return true;
342 public function create() {
343 $flags = array();
344 foreach ($this->_flags as $column => $flag) {
345 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
346 $flags[$column] = $flag;
349 $types = array_intersect_key($this->_types, $flags);
350 return $this->_storage->create($types, $flags);
353 public function drop() {
354 return $this->_storage->drop();
357 public function save() {
358 foreach ($this->data() as $row) {
359 switch ($row->flag()) {
360 case(AOOSMODELROW_PRIVATE_INSERT):
361 $this->_storage->insert($row->toDatabaseArray());
362 break;
363 case(AOOSMODELROW_PRIVATE_UPDATE):
364 $this->_storage->update($row->toDatabaseArray(), $row->old(), array(0,1));
365 break;
366 case(AOOSMODELROW_PRIVATE_REMOVE):
367 $this->_storage->remove($row->toDatabaseArray(), array(0,1));
368 break;
371 return true;
375 class AOOSModelRow extends AOOSModule implements ArrayAccess{
376 private $_data = array();
377 private $_flag = 0;
378 private $_old = null;
380 public function __construct($parent, $value = null, $flag = 0) {
381 parent::__construct($parent);
382 $this->setFlag($flag);
383 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
384 if (is_array($value)) {
385 $values = array_intersect_key($value, $this->_data);
386 foreach ($values as $index => $val) {
387 $this->_setData($index, $value[$index]);
392 public function __set($name, $value) {
393 return $this->_setData($name, $value);
396 public function __get($name) {
397 if (!$this->parent()->inColumnIndex($name)) {
398 throw new AOOSException($this->core(), $this->tr("index_error"));
400 return $this->_data[$name];
403 public function __toString() {
404 return print_r($this->_data, true);
407 public function offsetSet($name, $value) {
408 return $this->$name = $value;
411 public function offsetGet($name) {
412 return $this->$name;
415 public function offsetExists($name) {
416 return $this->parent()->inColumnIndex($name);
419 public function offsetUnset($name) {
420 if (!$this->parent()->inColumnIndex($name)) {
421 throw new AOOSException($this->core(), $this->tr("index_error"));
423 $this->$name = null;
424 return true;
427 public function match($match) {
428 $matches = array_intersect_key($match, $this->_data);
429 foreach ($matches as $key => $val) {
430 if (!preg_match("/".$val."/", $this->$key)) {
431 return false;
434 return true;
437 public function toArray() {
438 $data = $this->_data;
439 return $data;
442 public function toDatabaseArray($data = null) {
443 if ($data == null) {
444 $data = $this->_data;
446 foreach ($data as $key => $value) {
447 if (!($this->parent()->getFlags($key) & AOOSMODEL_FLAG_FROM_DATABASE)) {
448 unset($data[$key]);
449 continue;
451 $type = $this->parent()->getType($key);
452 if ($type == AOOSMODEL_TYPE_STRING || $type == AOOSMODEL_TYPE_TEXT) {
453 $data[$key] = "'".$value."'";
456 return $data;
460 public function fromArray($value) {
461 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
462 $values = array_intersect_key($value, $this->_data);
463 foreach ($values as $key => $value) {
464 $this->_data[$key] = $value;
466 return true;
469 public function old($db = true) {
470 return $db ? $this->toDatabaseArray($this->_old) : $this->_old;
473 public function save() {
474 return $this->parent()->save();
477 public function setFlag($flag) {
478 if (!is_integer($flag)) {
479 throw new AOOSException($this->core(), $this->tr("not_integer"));
481 $this->_flag = $flag;
482 return true;
485 public function flag() {
486 return $this->_flag;
489 /* ------ Private functions ----- */
490 private function _checkType($name, $value) {
491 $type = $this->parent()->getType($name);
492 switch($type) {
493 case(AOOSMODEL_TYPE_STRING):
494 case(AOOSMODEL_TYPE_TEXT):
495 return is_string($value);
496 break;
497 case(AOOSMODEL_TYPE_INTEGER):
498 $value = (int) $value;
499 return is_integer($value);
500 break;
501 case(AOOSMODEL_TYPE_BOOLEAN):
502 return is_bool($value);
503 break;
504 case(AOOSMODEL_TYPE_UNKNOWN):
505 return true;
506 break;
507 default:
508 return false;
509 break;
513 private function _checkFlags($name, $value) {
514 $flags = $this->parent()->getFlags($name);
515 if ($flags === false) {
516 return true;
518 if ($flags & AOOSMODEL_FLAG_UNIQUE) {
519 $column = $this->parent()->getColumn($name);
520 if (in_array($value, $column)) {
521 return false;
524 return true;
527 private function _doProperties($name, $value) {
528 $properties = $this->parent()->getProperties($name);
529 if ($properties === false) {
530 return $value;
532 if ($properties & AOOSMODEL_PROP_HASH) {
533 $value = hash("sha256", $value);
535 if ($properties & AOOSMODEL_PROP_NOHTML) {
536 $value = htmlspecialchars($value);
538 if ($properties & AOOSMODEL_PROP_ESCAPE) {
539 $value = mysql_real_escape_string($value);
541 if ($properties & AOOSMODEL_PROP_STRIP) {
542 $value = rtrim(trim($value));
544 return $value;
547 private function _doConstraints($name, $value) {
548 $constraints = $this->parent()->getConstraints($name);
549 if ($constraints === false) {
550 return $value;
552 foreach ($constraints as $constraint) {
553 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
554 continue;
556 $value = call_user_func(array($constraint[0], "execute"), $constraint[1], $value);
557 if (false === $value) {
558 throw new AOOSException($this->core(), $this->tr("constraint_fail"), $name);
559 return false;
562 return $value;
565 private function _setData($name, $value) {
566 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE) {
567 return false;
569 if (!$this->parent()->inColumnIndex($name)) {
570 throw new AOOSException($this->core(), $this->tr("index_error"));
572 if (!$this->_checkType($name, $value)) {
573 throw new AOOSException($this->core(), $this->tr("wrong_type"), $name);
575 try {
576 $value = $this->_doProperties($name, $value);
577 $value = $this->_doConstraints($name, $value);
578 } catch (AOOSException $e) {
579 throw $e;
581 if (!$this->_checkFlags($name, $value)) {
582 throw new AOOSException($this->core(), $this->tr("flags_failed"), $name);
584 if ($this->flag() == 0) {
585 $this->setFlag(AOOSMODELROW_PRIVATE_UPDATE);
586 $this->_old = $this->_data;
588 $this->_data[$name] = $value;
589 return true;