PageRenderTime 49ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/model.php

https://github.com/keiouu/Evelite
PHP | 398 lines | 317 code | 41 blank | 40 comment | 56 complexity | 8bf6e1ae2b100aad0208e762d9de4762 MD5 | raw file
  1. <?php
  2. /*
  3. * Tikapot Model System
  4. *
  5. * Copyright 2011, AUTHORS.txt
  6. * Licensed under the GNU General Public License version 3.
  7. * See LICENSE.txt
  8. */
  9. require_once(home_dir . "framework/database.php");
  10. require_once(home_dir . "framework/model_query.php");
  11. require_once(home_dir . "framework/model_fields/init.php");
  12. class ValidationException extends Exception { }
  13. class TableValidationException extends ValidationException { }
  14. class ModelExistsException extends Exception {}
  15. abstract class Model
  16. {
  17. private $from_db = False, $_valid_model = False;
  18. protected $fields = array(), $errors = array();
  19. public function __construct() {
  20. $this->add_field("id", new PKField(0, $max_length = 22, True));
  21. $this->_valid_model = True;
  22. }
  23. /* Hack for get and find */
  24. private static function get_temp_instance() {
  25. $obj = new static();
  26. $obj->_valid_model = False;
  27. return $obj;
  28. }
  29. /* Allows custom model queries */
  30. protected static function get_modelquery($query = array()) {
  31. return new ModelQuery(new static(), $query);
  32. }
  33. /* Allows custom primary keys */
  34. protected function _pk() {
  35. foreach ($this->fields as $name => $field)
  36. if (strtolower(get_class($field)) === "pkfield")
  37. return $name;
  38. }
  39. /* Format: array("COL"=>"VAL") */
  40. public function load_values($array) {
  41. foreach ($this->fields as $name => $field)
  42. if (array_key_exists($name, $array))
  43. $field->set_value($array[$name]);
  44. }
  45. /* Load field values from query result. Sets "from_db" to True */
  46. public function load_query_values($result) {
  47. $this->load_values($result);
  48. $this->from_db = True;
  49. }
  50. // Allows access to stored models
  51. // Returns all objects
  52. public static function all() {
  53. return static::get_modelquery();
  54. }
  55. // Allows access to stored models
  56. // Returns a modelquery object containing the elements
  57. // $query should be in the following format: (COL => Val, COL => (OPER => Val), etc)
  58. public static function find($query) {
  59. if (!is_array($query))
  60. throw new ValidationException("Model::find argument must be an array!");
  61. $parsed_query = array();
  62. $db = Database::create();
  63. $tempobj = static::get_temp_instance();
  64. $tempobj->create_table();
  65. foreach ($query as $col => $val) {
  66. if (!array_key_exists($col, $tempobj->fields))
  67. throw new ValidationException("Model::find keys must be valid field names!");
  68. $parsed_query[$col] = $tempobj->fields[$col]->sql_value($db, $val);
  69. }
  70. return static::get_modelquery(array("WHERE" => $parsed_query));
  71. }
  72. // Do the given object values exist in the database?
  73. public static function exists($query) {
  74. $lst = static::find($query);
  75. return count($len) > 0;
  76. }
  77. // Allows access to stored models
  78. // Returns a single object
  79. // Errors if multiple objects are found or no objects are found
  80. // Arg can be an id or an array with multiple parameters
  81. public static function get($arg = 0) {
  82. $results = NULL;
  83. if (is_array($arg))
  84. $results = static::find($arg);
  85. else {
  86. $tempobj = static::get_temp_instance();
  87. $results = static::find(array($tempobj->_pk() => $arg));
  88. }
  89. if ($results->count() == 0)
  90. throw new ModelQueryException("No objects matching query exist");
  91. if ($results->count() > 1)
  92. throw new ModelQueryException("Multiple objects matching query exist in get()");
  93. return $results->get(0);
  94. }
  95. // Allows access to stored models
  96. // Arg can be an id or an array with multiple search parameters
  97. // Returns an array containing: (a single object [creates it if needed], a boolean specifying weather or not the object is a new object)
  98. public static function get_or_create($arg = 0) {
  99. $obj = NULL;
  100. $created = False;
  101. try {
  102. $obj = static::get($arg);
  103. }
  104. catch (ModelQueryException $e) {
  105. $obj = new static();
  106. $obj->load_values($arg);
  107. $obj->save();
  108. $created = True;
  109. }
  110. return array($obj, $created);
  111. }
  112. public static function create($args = array()) {
  113. if (count($args) <= 0)
  114. return Null;
  115. try {
  116. $obj = static::get($args);
  117. }
  118. catch (ModelQueryException $e) {
  119. $obj = new static();
  120. $obj->load_values($args);
  121. $obj->save();
  122. return $obj;
  123. }
  124. throw new ModelExistsException("Error: Model already exists!");
  125. }
  126. // Add a new field
  127. protected function add_field($name, $type) {
  128. if (strtolower(get_class($type)) === "pkfield") {
  129. $new_fields = array();
  130. $new_fields[$name] = $type;
  131. foreach ($this->fields as $name => $field)
  132. if (strtolower(get_class($field)) !== "pkfield")
  133. $new_fields[$name] = $field;
  134. $this->fields = $new_fields;
  135. } else {
  136. $this->fields[$name] = $type;
  137. }
  138. }
  139. public function get_table_name() {
  140. return strtolower(get_class($this));
  141. }
  142. // Get fields
  143. public function get_fields() {
  144. return $this->fields;
  145. }
  146. // Get field
  147. public function get_field($name) {
  148. if ($name == "pk")
  149. $name = $this->_pk();
  150. return $this->fields[$name];
  151. }
  152. public function __get($name) {
  153. if ($name == "pk")
  154. $name = $this->_pk();
  155. if (isset($this->fields[$name]))
  156. return $this->fields[$name]->get_value();
  157. throw new Exception("Invalid model field '$name'.");
  158. }
  159. public function __set($name, $value) {
  160. if ($name == "pk")
  161. $name = $this->_pk();
  162. if (isset($this->fields[$name]))
  163. $this->fields[$name]->set_value($value);
  164. else
  165. throw new Exception("Invalid model field '$name'.");
  166. }
  167. // Basically: Is $name a valid field name? (Doesnt say if the field has been set)
  168. public function __isset($name) {
  169. if ($name == "pk")
  170. return True;
  171. return isset($this->fields[$name]);
  172. }
  173. // Unsetting a field resets it to default value
  174. public function __unset($name) {
  175. if ($name == "pk")
  176. return;
  177. if ($this->__isset($name))
  178. $this->fields[$name]->reset();
  179. }
  180. // Returns the query to create the table in the database
  181. public function db_create_query($db) {
  182. $table_name = $this->get_table_name();
  183. $post_scripts = "";
  184. $SQL = "CREATE TABLE \"" . $table_name . "\" (";
  185. $i = 0;
  186. foreach ($this->get_fields() as $name => $field) {
  187. if ($i > 0) $SQL .= ", ";
  188. $SQL .= $field->db_create_query($db, $name, $table_name);
  189. $i++;
  190. $post_query = $field->db_post_create_query($db, $name, $table_name);
  191. if (strlen($post_scripts) > 0 && strlen($post_query) > 0)
  192. $post_scripts .= ", ";
  193. if (strlen($post_query) > 0)
  194. $post_scripts .= $post_query;
  195. }
  196. if (strlen($post_scripts) > 0)
  197. $SQL .= ", " . $post_scripts;
  198. $SQL .= ");";
  199. return $SQL;
  200. }
  201. public function db_create_extra_queries_pre($db, $table_name) {
  202. $extra_scripts = array();
  203. foreach ($this->get_fields() as $name => $field) {
  204. $query = $field->db_extra_create_query_pre($db, $name, $table_name);
  205. if (strlen($query) > 0)
  206. array_push($extra_scripts, $query);
  207. }
  208. return $extra_scripts;
  209. }
  210. public function db_create_extra_queries_post($db, $table_name) {
  211. $extra_scripts = array();
  212. foreach ($this->get_fields() as $name => $field) {
  213. $query = $field->db_extra_create_query_post($db, $name, $table_name);
  214. if (strlen($query) > 0)
  215. array_push($extra_scripts, $query);
  216. }
  217. return $extra_scripts;
  218. }
  219. // Creates the table in the database if needed
  220. public function create_table() {
  221. $db = Database::create();
  222. $table_name = $this->get_table_name();
  223. if (!in_array($this->get_table_name(), $db->get_tables())) {
  224. foreach($this->db_create_extra_queries_pre($db, $table_name) as $query)
  225. $db->query($query);
  226. $res = $db->query($this->db_create_query($db));
  227. foreach($this->db_create_extra_queries_post($db, $table_name) as $query)
  228. $db->query($query);
  229. return $res;
  230. }
  231. return True;
  232. }
  233. // Verifies that the table structure in the database is up-to-date
  234. // NOTE: Currently only detects field name changes, not type changes
  235. public function verify_table() {
  236. $this->create_table();
  237. $db = Database::create();
  238. $table_name = $this->get_table_name();
  239. $fields = $this->get_fields();
  240. $columns = $db->get_columns($this->get_table_name());
  241. foreach ($columns as $column => $type) {
  242. if (!array_key_exists($column, $fields))
  243. throw new TableValidationException($column . " is no longer a part of " . $table_name);
  244. }
  245. foreach ($fields as $field => $type) {
  246. if (!array_key_exists($field, $columns))
  247. throw new TableValidationException($field . " should be in " . $table_name);
  248. }
  249. return True;
  250. }
  251. // Validates the model
  252. public function validate() {
  253. $this->errors = array();
  254. foreach ($this->get_fields() as $field_name => $field) {
  255. if (!$field->validate()) {
  256. $this->errors = array_merge($this->errors, $field->errors);
  257. return False;
  258. }
  259. }
  260. return True;
  261. }
  262. // Provides validation errors
  263. public function get_errors() {
  264. return $this->errors;
  265. }
  266. public function get_error_string() {
  267. $str = "";
  268. foreach ($this->get_errors() as $error) {
  269. if (strlen($str) > 0)
  270. $str .= "\n";
  271. $str .= $error;
  272. }
  273. return $str;
  274. }
  275. // Insert the object to the database
  276. public function insert_query($db) {
  277. $keys = "";
  278. $values = "";
  279. foreach ($this->get_fields() as $field_name => $field) {
  280. if ($field->hide_from_query)
  281. continue;
  282. if (strlen($keys) > 0) {
  283. $keys .= ", ";
  284. $values .= ", ";
  285. }
  286. $keys .= $field_name;
  287. $val = $field->sql_value($db);
  288. if (strlen($val) <= 0)
  289. $val = "''";
  290. $values .= $val;
  291. }
  292. $extra = "";
  293. if ($db->get_type() == "psql")
  294. $extra = " RETURNING " . $this->_pk();
  295. return "INSERT INTO \"" . $this->get_table_name() . "\" (" . $keys . ") VALUES (" . $values . ")" . $extra . ";";
  296. }
  297. // Insert the object to the database
  298. public function update_query($db) {
  299. $old_object = static::get($this->pk);
  300. $query = "UPDATE \"" . $this->get_table_name() . "\" SET ";
  301. $go = False;
  302. foreach ($old_object->get_fields() as $name => $field) {
  303. $new_val = $this->fields[$name];
  304. if (strval($field->get_value()) !== strval($new_val->get_value())) {
  305. if ($go)
  306. $query .= ", ";
  307. $query .= $name . "=" . $new_val->sql_value($db);
  308. $go = True;
  309. }
  310. }
  311. $query .= " WHERE " . $this->_pk() . "=" . $this->pk;
  312. if ($go)
  313. return $query;
  314. return ""; // Nothing to do
  315. }
  316. // Saves the object to the database, returns ID
  317. public function save() {
  318. if (!$this->_valid_model)
  319. throw new ValidationException("Error in save(): Model is not supposed to exist! (Perhaps you forgot to call parent constructor?) in " . get_class($this));
  320. if (!$this->validate())
  321. throw new ValidationException("Error in " . get_class($this) . "::save(): model did not validate! <br />" . $this->get_error_string());
  322. $this->create_table();
  323. $db = Database::create();
  324. $query = "";
  325. foreach ($this->get_fields() as $name => $field)
  326. $field->pre_save($this, $this->from_db);
  327. if (!$this->from_db) {
  328. $query = $db->query($this->insert_query($db));
  329. $id = 0;
  330. if ($db->get_type() == "psql") {
  331. $row = $db->fetch($query);
  332. $id = $row[0];
  333. }
  334. if ($db->get_type() == "mysql")
  335. $id = mysql_insert_id();
  336. $this->pk = intval($id);
  337. $this->from_db = True;
  338. }
  339. else {
  340. $query = $this->update_query($db);
  341. if (strlen($query) > 0)
  342. $db->query($query);
  343. }
  344. return $this->pk;
  345. }
  346. public function delete_query($db) {
  347. return "DELETE FROM \"" . $this->get_table_name() . "\" WHERE ". $this->_pk() ."='" . $this->pk . "';";
  348. }
  349. /* Returns True on success, False on failure */
  350. public function delete() {
  351. if (!$this->from_db)
  352. return False;
  353. $db = Database::create();
  354. $db->query($this->delete_query($db));
  355. }
  356. }
  357. ?>