Changeset 272 for trunk/trax/vendor/trax/active_record.php
- Timestamp:
- 01/18/07 08:49:09 (5 years ago)
- Files:
-
- 1 modified
-
trunk/trax/vendor/trax/active_record.php (modified) (51 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/trax/vendor/trax/active_record.php
r263 r272 73 73 * {@link http://pear.php.net/manual/en/package.database.mdb2.php PEAR MDB2} 74 74 */ 75 pr ivatestatic $db = null;75 protected static $db = null; 76 76 77 77 /** … … 177 177 178 178 /** 179 * Force reconnect to database 179 * Force reconnect to database every page load 180 180 * 181 181 * @var boolean 182 182 */ 183 public $force_reconnect = false; # should we force a connection everytime183 public $force_reconnect = false; 184 184 185 185 /** … … 187 187 * each object index is off of this field 188 188 * 189 * @var boolean189 * @var string 190 190 */ 191 191 public $index_on = "id"; … … 267 267 * @var string[] 268 268 */ 269 p rotected$auto_update_timestamps = array("updated_at","updated_on");269 public $auto_update_timestamps = array("updated_at","updated_on"); 270 270 271 271 /** … … 279 279 * @var string[] 280 280 */ 281 p rotected$auto_create_timestamps = array("created_at","created_on");281 public $auto_create_timestamps = array("created_at","created_on"); 282 282 283 283 /** … … 288 288 * @var string 289 289 */ 290 p rotected$date_format = "Y-m-d";290 public $date_format = "Y-m-d"; 291 291 292 292 /** … … 297 297 * @var string 298 298 */ 299 p rotected$time_format = "H:i:s";299 public $time_format = "H:i:s"; 300 300 301 301 /** … … 367 367 */ 368 368 public $errors = array(); 369 370 /** 371 * An array with all the default error messages. 372 */ 373 public $default_error_messages = array( 374 'inclusion' => "is not included in the list", 375 'exclusion' => "is reserved", 376 'invalid' => "is invalid", 377 'confirmation' => "doesn't match confirmation", 378 'accepted ' => "must be accepted", 379 'empty' => "can't be empty", 380 'blank' => "can't be blank", 381 'too_long' => "is too long (max is %d characters)", 382 'too_short' => "is too short (min is %d characters)", 383 'wrong_length' => "is the wrong length (should be %d characters)", 384 'taken' => "has already been taken", 385 'not_a_number' => "is not a number", 386 'not_an_integer' => "is not an integer" 387 ); 388 389 /** 390 * An array of all the builtin validation function calls. 391 */ 392 protected $builtin_validation_functions = array( 393 'validates_acceptance_of', 394 'validates_confirmation_of', 395 'validates_exclusion_of', 396 'validates_format_of', 397 'validates_inclusion_of', 398 'validates_length_of', 399 'validates_numericality_of', 400 'validates_presence_of', 401 'validates_uniqueness_of' 402 ); 403 404 /** 405 * An array of all the builtin validation function to validate on a save/create/update. 406 */ 407 public $builtins_to_validate = array(); 369 408 370 409 /** … … 433 472 434 473 # If $attributes array is passed in update the class with its contents 435 $this->update_attributes($attributes); 474 if(!is_null($attributes)) { 475 $this->update_attributes($attributes); 476 } 477 478 # If callback is defined in model run it. 479 # this could hurt performance... 480 if(method_exists($this, 'after_initialize')) { 481 $this->after_initialize(); 482 } 436 483 } 437 484 … … 497 544 //echo "setting: $key = $value<br>"; 498 545 if($key == "table_name") { 499 $this->set_content_columns($value); 500 # this elseif checks if first its an object if its parent is ActiveRecord546 $this->set_content_columns($value); 547 # this elseif checks if first its an object if its parent is ActiveRecord 501 548 } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations) { 502 549 if($association_type = $this->get_association_type($key)) { … … 672 719 $this_primary_key_value = $this->$this_primary_key; 673 720 } else { 674 $this_primary_key_value = 0; 721 #$this_primary_key_value = 0; 722 # no primary key value so just return empty array same as find_all() 723 return array(); 675 724 } 676 725 … … 753 802 $conditions = "{$foreign_key} = {$foreign_key_value}"; 754 803 } else { 755 $conditions = "{$foreign_key} = 0"; 804 #$conditions = "{$foreign_key} = 0"; 805 # no primary key value so just return empty array same as find_all() 806 return array(); 756 807 } 757 808 $conditions .= $additional_conditions; … … 811 862 $conditions = "{$foreign_key} = {$foreign_key_value}"; 812 863 } else { 813 $conditions = "{$foreign_key} = 0"; 864 #$conditions = "{$foreign_key} = 0"; 865 return null; 814 866 } 815 867 … … 817 869 818 870 # Get the list of other_class_name objects 819 $result = $other_class_object->find_first($conditions, $order); 820 821 # There should only be one result, an object, if so return it 822 return (is_object($result) ? $result : null); 871 return $other_class_object->find_first($conditions, $order); 823 872 } 824 873 … … 874 923 $conditions = "{$other_primary_key} = {$other_primary_key_value}"; 875 924 } else { 876 $conditions = "{$other_primary_key} = 0"; 925 #$conditions = "{$other_primary_key} = 0"; 926 return null; 877 927 } 878 928 $conditions .= $additional_conditions; 879 929 880 930 # Get the list of other_class_name objects 881 $result = $other_class_object->find_first($conditions, $order); 882 883 # There should only be one result, an object, if so return it 884 return (is_object($result) ? $result : null); 931 return $other_class_object->find_first($conditions, $order); 885 932 } 886 933 … … 912 959 $aggregate_type = strtoupper(substr($aggregate_type, 0, -4)); 913 960 ($parameters[0]) ? $field = $parameters[0] : $field = "*"; 914 $sql = "SELECT $aggregate_type($field) AS agg_result FROM $this->table_name";961 $sql = "SELECT {$aggregate_type}({$field}) AS agg_result FROM {$this->table_prefix}{$this->table_name} "; 915 962 916 963 # Use any passed-in parameters … … 951 998 $tables = array($first_table, $second_table); 952 999 @sort($tables); 953 return @implode("_", $tables);1000 return $this->table_prefix.@implode("_", $tables); 954 1001 } 955 1002 … … 1027 1074 if($this->column_attribute_exists($column) && ($conditions = $this->get_primary_key_conditions())) { 1028 1075 # Run the query to grab a specific columns value. 1029 $sql = "SELECT {$column} FROM {$this->table_ name} WHERE {$conditions}";1076 $sql = "SELECT {$column} FROM {$this->table_prefix}{$this->table_name} WHERE {$conditions}"; 1030 1077 $this->log_query($sql); 1031 1078 $result = self::$db->queryOne($sql); … … 1178 1225 } 1179 1226 1180 /** 1181 * Return rows selected by $conditions 1182 * 1183 * If no rows match, an empty array is returned. 1184 * @param string SQL to use in the query. If 1185 * $conditions contains "SELECT", then $order, $limit and 1186 * $joins are ignored and the query is completely specified by 1187 * $conditions. If $conditions is omitted or does not contain 1188 * "SELECT", "SELECT * FROM" will be used. If $conditions is 1189 * specified and does not contain "SELECT", the query will 1190 * include "WHERE $conditions". If $conditions is null, the 1191 * entire table is returned. 1192 * @param string Argument to "ORDER BY" in query. 1193 * If specified, the query will include 1194 * "ORDER BY $order". If omitted, no ordering will be 1195 * applied. 1196 * @param integer[] Page, rows per page??? 1197 * @param string ??? 1198 * @todo Document the $limit and $joins parameters 1227 /** 1228 * Builds a sql statement. 1229 * 1199 1230 * @uses $rows_per_page_default 1200 1231 * @uses $rows_per_page 1201 1232 * @uses $offset 1202 1233 * @uses $page 1203 * @uses is_error() 1204 * @uses $new_record 1205 * @uses query() 1206 * @return object[] Array of objects of the same class as this 1207 * object, one object for each row returned by the query. 1208 * If the column 'id' was in the results, it is used as the key 1209 * for that object in the array. 1210 * @throws {@link ActiveRecordError} 1211 */ 1212 function find_all($conditions = null, $order = null, $limit = null, $joins = null) { 1213 //error_log("find_all(".(is_null($conditions)?'null':$conditions) 1214 // .', ' . (is_null($order)?'null':$order) 1215 // .', ' . (is_null($limit)?'null':var_export($limit,true)) 1216 // .', ' . (is_null($joins)?'null':$joins).')'); 1217 1218 $offset = null; 1234 * 1235 */ 1236 function build_sql($conditions = null, $order = null, $limit = null, $joins = null) { 1237 1238 $offset = null; 1219 1239 $per_page = null; 1220 1240 $select = null; … … 1244 1264 if(is_null($select)) { 1245 1265 $select = "*"; 1246 } 1266 } 1247 1267 1248 1268 # SQL will be built from specifications in argument 1249 $sql = "SELECT {$select} FROM {$this->table_ name} ";1269 $sql = "SELECT {$select} FROM {$this->table_prefix}{$this->table_name} "; 1250 1270 1251 1271 # If join specified, include it … … 1277 1297 $this->rows_per_page = $this->rows_per_page_default; 1278 1298 } 1279 1280 $this->page = $_REQUEST['page']; 1299 1300 # Only use request's page if you are calling from find_all_with_pagination() and if it is int 1301 if(strval(intval($_REQUEST['page'])) == $_REQUEST['page']) { 1302 $this->page = $_REQUEST['page']; 1303 } 1304 1281 1305 if($this->page <= 0) { 1282 1306 $this->page = 1; … … 1284 1308 1285 1309 # Set the LIMIT string segment for the SQL 1286 if(!$offset) {1310 if(is_null($offset)) { 1287 1311 $offset = ($this->page - 1) * $this->rows_per_page; 1288 1312 } … … 1290 1314 $sql .= "LIMIT {$this->rows_per_page} OFFSET {$offset}"; 1291 1315 # $sql .= "LIMIT $offset, $this->rows_per_page"; 1292 1293 # Set number of total pages in result set 1294 if($count = $this->count_all($this->primary_keys[0], $conditions, $joins)) { 1295 $this->pagination_count = $count; 1296 $this->pages = ( 1297 ($count % $this->rows_per_page) == 0) 1298 ? $count / $this->rows_per_page 1299 : floor($count / $this->rows_per_page) + 1; 1300 } 1301 } 1302 } 1316 1317 # Set number of total pages in result set 1318 if($count = $this->count_all($this->primary_keys[0], $conditions, $joins)) { 1319 $this->pagination_count = $count; 1320 $this->pages = (($count % $this->rows_per_page) == 0) 1321 ? $count / $this->rows_per_page 1322 : floor($count / $this->rows_per_page) + 1; 1323 } 1324 } 1325 } 1326 1327 return $sql; 1328 } 1329 1330 /** 1331 * Return rows selected by $conditions 1332 * 1333 * If no rows match, an empty array is returned. 1334 * @param string SQL to use in the query. If 1335 * $conditions contains "SELECT", then $order, $limit and 1336 * $joins are ignored and the query is completely specified by 1337 * $conditions. If $conditions is omitted or does not contain 1338 * "SELECT", "SELECT * FROM" will be used. If $conditions is 1339 * specified and does not contain "SELECT", the query will 1340 * include "WHERE $conditions". If $conditions is null, the 1341 * entire table is returned. 1342 * @param string Argument to "ORDER BY" in query. 1343 * If specified, the query will include 1344 * "ORDER BY $order". If omitted, no ordering will be 1345 * applied. 1346 * @param integer[] Page, rows per page??? 1347 * @param string ??? 1348 * @todo Document the $limit and $joins parameters 1349 * @uses is_error() 1350 * @uses $new_record 1351 * @uses query() 1352 * @return object[] Array of objects of the same class as this 1353 * object, one object for each row returned by the query. 1354 * If the column 'id' was in the results, it is used as the key 1355 * for that object in the array. 1356 * @throws {@link ActiveRecordError} 1357 */ 1358 function find_all($conditions = null, $order = null, $limit = null, $joins = null) { 1359 //error_log("find_all(".(is_null($conditions)?'null':$conditions) 1360 // .', ' . (is_null($order)?'null':$order) 1361 // .', ' . (is_null($limit)?'null':var_export($limit,true)) 1362 // .', ' . (is_null($joins)?'null':$joins).')'); 1363 1364 # Placed the sql building code in a separate function 1365 $sql = $this->build_sql($conditions, $order, $limit, $joins); 1303 1366 1304 1367 # echo "ActiveRecord::find_all() - sql: $sql\n<br>"; … … 1323 1386 $objects[$objects_key] = $object; 1324 1387 unset($object); 1388 # If callback is defined in model run it. 1389 # this will probably hurt performance... 1390 if(method_exists($this, 'after_find')) { 1391 $this->after_find(); 1392 } 1325 1393 } 1326 1394 return $objects; … … 1423 1491 * @throws {@link ActiveRecordError} 1424 1492 */ 1425 function find_first($conditions = null, $order = null, $limit = null, $joins = null) {1493 function find_first($conditions = null, $order = null, $limit = 1, $joins = null) { 1426 1494 if(is_array($conditions)) { 1427 1495 $options = $conditions; … … 1433 1501 if(!is_null($joins)) $options['joins'] = $joins; 1434 1502 1435 $result = $this->find_all($options);1436 return @current($result);1503 $result = @current($this->find_all($options)); 1504 return (is_object($result) ? $result : null); 1437 1505 } 1438 1506 … … 1515 1583 */ 1516 1584 function update_all($updates, $conditions = null) { 1517 $sql = "UPDATE $this->table_name SET $updates WHERE $conditions";1585 $sql = "UPDATE {$this->table_prefix}{$this->table_name} SET {$updates} WHERE {$conditions}"; 1518 1586 $result = $this->query($sql); 1519 1587 if ($this->is_error($result)) { … … 1621 1689 self::$db->loadModule('Extended', null, true); 1622 1690 # $primary_key_value may either be a quoted integer or php null 1623 $primary_key_value = self::$db->getBeforeID( $this->table_name, $this->primary_keys[0]);1691 $primary_key_value = self::$db->getBeforeID("{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]); 1624 1692 if($this->is_error($primary_key_value)) { 1625 1693 $this->raise($primary_key_value->getMessage()); … … 1629 1697 $fields = @implode(', ', array_keys($attributes)); 1630 1698 $values = @implode(', ', array_values($attributes)); 1631 $sql = "INSERT INTO {$this->table_ name} ($fields) VALUES ($values)";1699 $sql = "INSERT INTO {$this->table_prefix}{$this->table_name} ({$fields}) VALUES ({$values})"; 1632 1700 //echo "add_record: SQL: $sql<br>"; 1633 1701 //error_log("add_record: SQL: $sql"); … … 1640 1708 $primary_key = $this->primary_keys[0]; 1641 1709 # $id is now equivalent to the value in the id field that was inserted 1642 $primary_key_value = self::$db->getAfterID($primary_key_value, $this->table_name, $this->primary_keys[0]);1710 $primary_key_value = self::$db->getAfterID($primary_key_value, "{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]); 1643 1711 if($this->is_error($primary_key_value)) { 1644 1712 $this->raise($primary_key_value->getMessage()); … … 1681 1749 $updates = $this->get_updates_sql(); 1682 1750 $conditions = $this->get_primary_key_conditions(); 1683 $sql = "UPDATE {$this->table_ name} SET {$updates} WHERE {$conditions}";1751 $sql = "UPDATE {$this->table_prefix}{$this->table_name} SET {$updates} WHERE {$conditions}"; 1684 1752 //echo "update_record:$sql<br>"; 1685 1753 //error_log("update_record: SQL: $sql"); … … 1920 1988 1921 1989 # Delete the record(s) 1922 if($this->is_error($rs = $this->query("DELETE FROM $this->table_name WHERE $conditions"))) {1990 if($this->is_error($rs = $this->query("DELETE FROM {$this->table_prefix}{$this->table_name} WHERE {$conditions}"))) { 1923 1991 $this->raise($rs->getMessage()); 1924 1992 } … … 2084 2152 function update_attributes($attributes) { 2085 2153 //error_log('update_attributes()'); 2086 2087 2154 if(is_array($attributes)) { 2155 $datetime_fields = array(); 2088 2156 // Test each attribute to be updated 2089 2157 // and process according to its type … … 2091 2159 # datetime / date parts check 2092 2160 if(preg_match('/^\w+\(.*i\)$/i', $field)) { 2093 2094 2161 // The name of this attribute ends in "(?i)" 2095 2162 // indicating that it's part of a date or time 2096 2097 // Accumulate all the pieces of a date and time in 2098 // array $datetime_key. The keys in the array are 2099 // the names of date/time attributes with the final 2100 // "(?i)" stripped off. 2101 $datetime_key = substr($field, 0, strpos($field, "(")); 2102 if( !isset($old_datetime_key) 2103 || ($datetime_key != $old_datetime_key)) { 2104 2105 // This value of $datetime_key hasn't been seen 2106 // before, so remember it. 2107 $old_datetime_key = $datetime_key; 2108 2109 // $datetime_value accumulates the pieces of the 2110 // date/time attribute $datetime_key 2111 $datetime_value = ""; 2112 } 2113 2114 // Concatentate pieces of the attribute's value 2115 // FIXME: this only works if the array elements 2116 // are sorted by key. Is this guaranteed? 2117 if(strstr($field, "2i") || strstr($field, "3i")) { 2118 $datetime_value .= "-".$value; 2119 } elseif(strstr($field, "4i")) { 2120 $datetime_value .= " ".$value; 2121 } elseif(strstr($field, "5i")) { 2122 $datetime_value .= ":".$value; 2123 } else { 2124 $datetime_value .= $value; 2125 } 2126 $datetime_fields[$old_datetime_key] = $datetime_value; 2163 $datetime_field = substr($field, 0, strpos($field, "(")); 2164 if(!in_array($datetime_field, $datetime_fields)) { 2165 $datetime_fields[] = $datetime_field; 2166 } 2127 2167 # this elseif checks if first its an object if its parent is ActiveRecord 2128 2168 } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations) { … … 2141 2181 } 2142 2182 } else { 2143 2144 2183 // Just a simple attribute, copy it 2145 2184 $this->$field = $value; … … 2149 2188 // If any date/time fields were found, assign the 2150 2189 // accumulated values to corresponding attributes 2151 if(isset($datetime_fields) 2152 && is_array($datetime_fields)) { 2153 foreach($datetime_fields as $field => $value) { 2154 //error_log("$field = $value"); 2155 $this->$field = $value; 2190 if(count($datetime_fields)) { 2191 foreach($datetime_fields as $datetime_field) { 2192 $datetime_format = ''; 2193 $datetime_value = ''; 2194 if($attributes[$datetime_field."(1i)"] 2195 && $attributes[$datetime_field."(2i)"] 2196 && $attributes[$datetime_field."(3i)"]) { 2197 $datetime_value = $attributes[$datetime_field."(1i)"] 2198 . "-" . $attributes[$datetime_field."(2i)"] 2199 . "-" . $attributes[$datetime_field."(3i)"]; 2200 $datetime_format = $this->date_format; 2201 } 2202 $datetime_value .= " "; 2203 if($attributes[$datetime_field."(4i)"] 2204 && $attributes[$datetime_field."(5i)"]) { 2205 $datetime_value .= $attributes[$datetime_field."(4i)"] 2206 . ":" . $attributes[$datetime_field."(5i)"]; 2207 $datetime_format .= " ".$this->time_format; 2208 } 2209 if($datetime_value = trim($datetime_value)) { 2210 $datetime_value = date($datetime_format, strtotime($datetime_value)); 2211 //error_log("($field) $datetime_field = $datetime_value"); 2212 $this->$datetime_field = $datetime_value; 2213 } 2156 2214 } 2157 2215 } … … 2226 2284 } 2227 2285 $return = array(); 2228 foreach($attributes as $key => $value) { 2229 $value = $this->check_datetime($key, $value); 2230 $column = $this->column_for_attribute($key); 2231 $type = $this->attribute_is_string($key, $column) ? "Text" : "Integer"; 2232 $value = self::$db->quote($value, $type); 2233 if($value == 'NULL' && stristr($column['flags'], "not_null")) { 2234 $value = "''"; 2235 } 2236 $return[$key] = $value; 2286 foreach($attributes as $name => $value) { 2287 $return[$name] = $this->quote_attribute($name, $value); 2237 2288 } 2238 2289 return $return; 2239 2290 } 2291 2292 /** 2293 * Quotes a single attribute for use in an sql statement. 2294 * 2295 */ 2296 function quote_attribute($attribute, $value = null) { 2297 $value = is_null($value) ? $this->$attribute : $value; 2298 $value = $this->check_datetime($attribute, $value); 2299 $column = $this->column_for_attribute($attribute); 2300 if(isset($column['mdb2type'])) { 2301 $type = $column['mdb2type']; 2302 } else { 2303 $type = $this->attribute_is_string($attribute, $column) ? 2304 "text" : is_float($attribute) ? "float" : "integer"; 2305 } 2306 $value = self::$db->quote($value, $type); 2307 if($value === 'NULL' && stristr($column['flags'], "not_null")) { 2308 $value = "''"; 2309 } 2310 return $value; 2311 } 2312 2313 /** 2314 * Escapes a string for use in an sql statement. 2315 * 2316 */ 2317 function escape($string) { 2318 return(self::$db->escape($string)); 2319 } 2240 2320 2241 2321 /** … … 2270 2350 * Example: if $primary_keys = array("id", "ssn") and column "id" 2271 2351 * has value "5" and column "ssn" has value "123-45-6789" then 2272 * the string "id = '5'AND ssn = '123-45-6789'" would be returned.2352 * the string "id = 5 AND ssn = '123-45-6789'" would be returned. 2273 2353 * @uses $primary_keys 2274 2354 * @uses quoted_attributes() 2275 2355 * @return string Column name = 'value' [ AND name = 'value']... 2276 2356 */ 2277 function get_primary_key_conditions( ) {2357 function get_primary_key_conditions($operator = "=") { 2278 2358 $conditions = null; 2279 2359 $attributes = $this->quoted_attributes(); … … 2283 2363 foreach($attributes as $key => $value) { 2284 2364 if(in_array($key, $this->primary_keys) && isset($value) && $value != "''") { 2285 $conditions[] = " $key = $value";2365 $conditions[] = "{$key} {$operator} {$value}"; 2286 2366 } 2287 2367 } … … 2338 2418 */ 2339 2419 private function get_class_name() { 2340 if(!is_null($this->class_name)) { 2341 $class_name = $this->class_name; 2342 } else { 2343 $class_name = get_class($this); 2344 } 2345 return $class_name; 2420 return !is_null($this->class_name) ? $this->class_name : get_class($this); 2346 2421 } 2347 2422 … … 2361 2436 */ 2362 2437 function set_content_columns($table_name) { 2438 if(!is_null($this->table_prefix)) { 2439 $table_name = $this->table_prefix.$table_name; 2440 } 2363 2441 if(isset(self::$table_info[$table_name])) { 2364 2442 $this->content_columns = self::$table_info[$table_name]; … … 2373 2451 foreach($this->content_columns as $column) { 2374 2452 $this->content_columns[$i++]['human_name'] = Inflector::humanize($column['name']); 2375 } 2453 } 2376 2454 self::$table_info[$table_name] = $this->content_columns; 2377 2455 } … … 2390 2468 // fetch the last inserted id via autoincrement or current value of a sequence 2391 2469 if(self::$db->supports('auto_increment') === true) { 2392 $id = self::$db->lastInsertID( $this->table_name, $this->primary_keys[0]);2470 $id = self::$db->lastInsertID("{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]); 2393 2471 if($this->is_error($id)) { 2394 2472 $this->raise($id->getMessage()); … … 2427 2505 $connection =& self::$active_connections[$this->connection_name]; 2428 2506 if(!is_object($connection) || $this->force_reconnect) { 2507 $connection_settings = array(); 2508 $connection_options = array(); 2429 2509 if(array_key_exists($this->connection_name, self::$database_settings)) { 2430 2510 # Use a different custom sections settings ? … … 2465 2545 2466 2546 /** 2467 * Test whether argument is a PEAR Error object or a MDB2 Error object.2468 *2469 * @param object $obj Object to test2470 * @return boolean Whether object is one of these two errors2471 */2472 function is_error($obj) {2473 if((PEAR::isError($obj)) || (MDB2::isError($obj))) {2474 return true;2475 } else {2476 return false;2477 }2478 }2479 2480 /**2481 * Throw an exception describing an error in this object2482 *2483 * @throws {@link ActiveRecordError}2484 */2485 function raise($message) {2486 $error_message = "Model Class: ".$this->get_class_name()."<br>";2487 $error_message .= "Error Message: ".$message;2488 throw new ActiveRecordError($error_message, "ActiveRecord Error", "500");2489 }2490 2491 /**2492 * Add or overwrite description of an error to the list of errors2493 * @param string $error Error message text2494 * @param string $key Key to associate with the error (in the2495 * simple case, column name). If omitted, numeric keys will be2496 * assigned starting with 0. If specified and the key already2497 * exists in $errors, the old error message will be overwritten2498 * with the value of $error.2499 * @uses $errors2500 */2501 function add_error($error, $key = null) {2502 if(!is_null($key)) {2503 $this->errors[$key] = $error;2504 } else {2505 $this->errors[] = $error;2506 }2507 }2508 2509 /**2510 * Return description of non-fatal errors2511 *2512 * @uses $errors2513 * @param boolean $return_string2514 * <ul>2515 * <li>true => Concatenate all error descriptions into a string2516 * using $seperator between elements and return the2517 * string</li>2518 * <li>false => Return the error descriptions as an array</li>2519 * </ul>2520 * @param string $seperator String to concatenate between error2521 * descriptions if $return_string == true2522 * @return mixed Error description(s), if any2523 */2524 function get_errors($return_string = false, $seperator = "<br>") {2525 if($return_string && count($this->errors) > 0) {2526 return implode($seperator, $this->errors);2527 } else {2528 return $this->errors;2529 }2530 }2531 2532 /**2533 * Return errors as a string.2534 *2535 * Concatenate all error descriptions into a stringusing2536 * $seperator between elements and return the string.2537 * @param string $seperator String to concatenate between error2538 * descriptions2539 * @return string Concatenated error description(s), if any2540 */2541 function get_errors_as_string($seperator = "<br>") {2542 return $this->get_errors(true, $seperator);2543 }2544 2545 /**2546 2547 * Determine if passed in attribute (table column) is a string 2547 2548 * @param string $attribute Name of the table column … … 2589 2590 * @uses validate(); 2590 2591 * @uses validate_model_attributes(); 2592 * @uses validate_builtin(); 2591 2593 * @uses validate_on_create(); 2592 2594 * @return boolean … … 2606 2608 $this->validate(); 2607 2609 $this->validate_model_attributes(); 2610 $this->validate_builtin(); 2608 2611 $this->after_validation(); 2609 2612 $this->validate_on_create(); … … 2614 2617 $this->validate(); 2615 2618 $this->validate_model_attributes(); 2619 $this->validate_builtin(); 2616 2620 $this->after_validation(); 2617 2621 $this->validate_on_update(); 2622 $this->validate_on_update_builtin(); 2618 2623 $this->after_validation_on_update(); 2619 2624 } … … 2676 2681 } 2677 2682 return $validated_ok; 2678 } 2679 2683 } 2684 2680 2685 /** 2681 2686 * Overwrite this method for validation checks on all saves and … … 2781 2786 */ 2782 2787 function after_delete() {} 2788 2789 2790 /** 2791 * Validates any builtin validates_* functions defined as 2792 * class variables in child model class. 2793 * 2794 * eg. 2795 * public $validates_presence_of = array( 2796 * 'first_name' => array( 2797 * 'message' => "is not optional.", 2798 * 'on' => 'update' 2799 * ), 2800 * 'last_name' => null, 2801 * 'password' => array( 2802 * 'on' => 'create' 2803 * ) 2804 * ); 2805 * 2806 * @uses $builtin_validation_functions 2807 */ 2808 function validate_builtin() { 2809 foreach($this->builtin_validation_functions as $method_name) { 2810 $validation_name = $this->$method_name; 2811 if(method_exists($this, $method_name) && is_array($validation_name)) { 2812 foreach($validation_name as $attribute_name => $options) { 2813 if(!is_array($options)) { 2814 $options = array(); 2815 } 2816 $parameters = array(); 2817 $on = array_key_exists('on', $options) ? 2818 $options['on'] : 'save'; 2819 $message = array_key_exists('message', $options) ? 2820 $options['message'] : null; 2821 switch($method_name) { 2822 case 'validates_acceptance_of': 2823 $accept = array_key_exists('accept', $options) ? $options['accept'] : 1; 2824 $parameters = array($attribute_name, $message, $accept); 2825 break; 2826 case 'validates_confirmation_of': 2827 $parameters = array($attribute_name, $message); 2828 break; 2829 case 'validates_exclusion_of': 2830 $in = array_key_exists('in', $options) ? $options['in'] : array(); 2831 $parameters = array($attribute_name, $in, $message); 2832 break; 2833 case 'validates_format_of': 2834 $with = array_key_exists('with', $options) ? $options['with'] : ''; 2835 $parameters = array($attribute_name, $with, $message); 2836 break; 2837 case 'validates_inclusion_of': 2838 $in = array_key_exists('in', $options) ? $options['in'] : array(); 2839 $parameters = array($attribute_name, $in, $message); 2840 break; 2841 case 'validates_length_of': 2842 $parameters = array($attribute_name, $options); 2843 break; 2844 case 'validates_numericality_of': 2845 $only_integer = array_key_exists('only_integer', $options) ? 2846 $options['only_integer'] : false; 2847 $allow_null = array_key_exists('allow_null', $options) ? 2848 $options['allow_null'] : false; 2849 $parameters = array($attribute_name, $message, $only_integer, $allow_null); 2850 break; 2851 case 'validates_presence_of': 2852 $parameters = array($attribute_name, $message); 2853 break; 2854 case 'validates_uniqueness_of': 2855 $parameters = array($attribute_name, $message); 2856 break; 2857 } 2858 if(count($parameters)) { 2859 $call = false; 2860 if($on == 'create' && $this->new_record) { 2861 $call = true; 2862 } elseif($on == 'update' && !$this->new_record) { 2863 $call = true; 2864 } elseif($on == 'save') { 2865 $call = true; 2866 } 2867 if($call) { 2868 # error_log("calling $method_name(".implode(",",$parameters).")"); 2869 call_user_func_array(array($this, $method_name), $parameters); 2870 } 2871 } 2872 } 2873 } 2874 } 2875 } 2876 2877 /** 2878 * Validates that a checkbox is clicked. 2879 * eg. validates_acceptance_of('eula') 2880 * 2881 * @param string|array $attribute_names 2882 * @param string $message 2883 * @param string $accept 2884 */ 2885 function validates_acceptance_of($attribute_names, $message = null, $accept = 1) { 2886 $message = $this->get_error_message_for_validation($message, 'acceptance'); 2887 foreach((array) $attribute_names as $attribute_name) { 2888 if($this->$attribute_name != $accept) { 2889 $attribute_human = Inflector::humanize($attribute_name); 2890 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2891 } 2892 } 2893 } 2894 2895 /** 2896 * Validates that a field has the same value as its corresponding confirmation field. 2897 * eg. validates_confirmation_of('password') 2898 * 2899 * @param string|array $attribute_names 2900 * @param string $message 2901 */ 2902 function validates_confirmation_of($attribute_names, $message = null) { 2903 $message = $this->get_error_message_for_validation($message, 'confirmation'); 2904 foreach((array) $attribute_names as $attribute_name) { 2905 $attribute_confirmation = $attribute_name . '_confirmation'; 2906 if($this->$attribute_confirmation != $this->$attribute_name) { 2907 $attribute_human = Inflector::humanize($attribute_name); 2908 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2909 } 2910 } 2911 } 2912 2913 /** 2914 * Validates that specified attributes are NOT in an array of elements. 2915 * eg. validates_exclusion_of('age, 'in' => array(13, 19)) 2916 * 2917 * @param string|array $attribute_names 2918 * @param mixed $in array(1,2,3,4,5) or string 1..5 2919 * @param string $message 2920 */ 2921 function validates_exclusion_of($attribute_names, $in = array(), $message = null) { 2922 $message = $this->get_error_message_for_validation($message, 'exclusion'); 2923 foreach((array) $attribute_names as $attribute_name) { 2924 if(is_string($in)) { 2925 list($minimum, $maximum) = explode('..', $in); 2926 if($this->$attribute_name >= $minimum && $this->$attribute_name <= $maximum) { 2927 $attribute_human = Inflector::humanize($attribute_name); 2928 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2929 } 2930 } elseif(is_array($in)) { 2931 if(in_array($this->$attribute_name, $in)) { 2932 $attribute_human = Inflector::humanize($attribute_name); 2933 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2934 } 2935 } 2936 } 2937 } 2938 2939 /** 2940 * Validates that specified attributes matches a regular expression 2941 * eg. validates_format_of('email', '/^(+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i') 2942 * 2943 * @param string|array $attribute_names 2944 * @param string $regex 2945 * @param string $message 2946 */ 2947 function validates_format_of($attribute_names, $regex, $message = null) { 2948 $message = $this->get_error_message_for_validation($message, 'invalid'); 2949 foreach((array) $attribute_names as $attribute_name) { 2950 $value = $this->$attribute_name; 2951 # Was there an error? 2952 if(!preg_match($regex, $value)) { 2953 $attribute_human = Inflector::humanize($attribute_name); 2954 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2955 } 2956 } 2957 } 2958 2959 /** 2960 * Validates that specified attributes are in an array of elements. 2961 * eg. validates_inclusion_of('gender', array('m', 'f')) 2962 * 2963 * @param string|array $attribute_names 2964 * @param mixed $in array(1,2,3,4,5) or string 1..5 2965 * @param string $message 2966 */ 2967 function validates_inclusion_of($attribute_names, $in = array(), $message = null) { 2968 $message = $this->get_error_message_for_validation($message, 'inclusion'); 2969 foreach((array) $attribute_names as $attribute_name) { 2970 if(is_string($in)) { 2971 list($minimum, $maximum) = explode('..', $in); 2972 if(!($this->$attribute_name >= $minimum && $this->$attribute_name <= $maximum)) { 2973 $attribute_human = Inflector::humanize($attribute_name); 2974 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2975 } 2976 } elseif(is_array($in)) { 2977 if(!in_array($this->$attribute_name, $in)) { 2978 $attribute_human = Inflector::humanize($attribute_name); 2979 $this->add_error("{$attribute_human} {$message}", $attribute_name); 2980 } 2981 } 2982 } 2983 } 2984 2985 /** 2986 * Validates that specified attributes are of some length 2987 * eg. validates_length_of('password', array('minimum' => 8)) 2988 * 2989 * @param string|array $attribute_names 2990 * @param array $options 2991 */ 2992 function validates_length_of($attribute_names, $options = array( 2993 'too_short' => null, 'too_long' => null, 'wrong_length' => null, 'message' => null)) { 2994 # Convert 'in' to 'minimum' and 'maximum' 2995 if(isset($options['in'])) { 2996 list($options['minimum'], $options['maximum']) = explode('..', $options['in']); 2997 } 2998 # If 'message' is set see if we need to override other messages 2999 if(isset($options['message'])) { 3000 if(!isset($options['too_short'])) $options['too_short'] = $options['message']; 3001 if(!isset($options['too_long'])) $options['too_long'] = $options['message']; 3002 if(!isset($options['wrong_length'])) $options['wrong_length'] = $options['message']; 3003 } 3004 3005 foreach((array) $attribute_names as $attribute_name) { 3006 # Attribute string length 3007 $len = strlen($this->$attribute_name); 3008 $attribute_human = Inflector::humanize($attribute_name); 3009 3010 # If you have set the min length option 3011 if(isset($options['minimum'])) { 3012 $message = $this->get_error_message_for_validation($options['too_short'], 'too_short', $options['minimum']); 3013 if($len < $options['minimum']) { 3014 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3015 } 3016 } 3017 3018 # If you have set the max length option 3019 if(isset($options['maximum'])) { 3020 $message = $this->get_error_message_for_validation($options['too_long'], 'too_long', $options['maximum']); 3021 if($len > $options['maximum']) { 3022 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3023 } 3024 } 3025 3026 # If you have set an exact length option 3027 if(isset($options['is'])) { 3028 $message = $this->get_error_message_for_validation($options['wrong_length'], 'wrong_length', $options['is']); 3029 if($len != $options['is']) { 3030 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3031 } 3032 } 3033 } 3034 } 3035 3036 /** 3037 * Validates that specified attributes are numbers 3038 * eg. validates_numericality_of('value') 3039 * 3040 * @param string|array $attribute_names 3041 * @param string $message 3042 */ 3043 function validates_numericality_of($attribute_names, $message = null, $only_integer = false, $allow_null = false) { 3044 foreach((array) $attribute_names as $attribute_name) { 3045 $value = $this->$attribute_name; 3046 # Skip validation if you allow null 3047 if($allow_null && is_null($value)) { 3048 break; 3049 } 3050 if($only_integer) { 3051 $message = $this->get_error_message_for_validation($message, 'not_an_integer'); 3052 if(!is_integer($value)) { 3053 $attribute_human = Inflector::humanize($attribute_name); 3054 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3055 } 3056 } else { 3057 $message = $this->get_error_message_for_validation($message, 'not_a_number'); 3058 if(!is_numeric($value)) { 3059 $attribute_human = Inflector::humanize($attribute_name); 3060 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3061 } 3062 } 3063 } 3064 } 3065 3066 /** 3067 * Validates that specified attributes are not blank 3068 * eg. validates_presence_of(array('firstname', 'lastname')) 3069 * 3070 * @param string|array $attribute_names 3071 * @param string $message 3072 */ 3073 function validates_presence_of($attribute_names, $message = null) { 3074 $message = $this->get_error_message_for_validation($message, 'empty'); 3075 foreach((array) $attribute_names as $attribute_name) { 3076 if($this->$attribute_name === '' || is_null($this->$attribute_name)) { 3077 $attribute_human = Inflector::humanize($attribute_name); 3078 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3079 } 3080 } 3081 } 3082 3083 /** 3084 * Validates that specified attributes are unique in the model database table 3085 * eg. validates_uniqueness_of('username') 3086 * 3087 * @param string|array $attribute_names 3088 * @param string $message 3089 */ 3090 function validates_uniqueness_of($attribute_names, $message = null) { 3091 $message = $this->get_error_message_for_validation($message, 'taken'); 3092 foreach((array) $attribute_names as $attribute_name) { 3093 $quoted_value = $this->quote_attribute($attribute_name); 3094 # Conditions for new and existing record 3095 if($this->new_record) { 3096 $conditions = sprintf("%s = %s", $attribute_name, $quoted_value); 3097 } else { 3098 $conditions = sprintf("%s = %s AND %s", $attribute_name, 3099 $quoted_value, $this->get_primary_key_conditions("!=")); 3100 } 3101 if($this->find_first($conditions)) { 3102 $attribute_human = Inflector::humanize($attribute_name); 3103 $this->add_error("{$attribute_human} {$message}", $attribute_name); 3104 } 3105 } 3106 } 3107 3108 /** 3109 * Return the error message for a validation function 3110 * 3111 * @param string $message 3112 * @param string $key 3113 * @param string $value 3114 * @return string 3115 */ 3116 private function get_error_message_for_validation($message, $key, $value = null) { 3117 if(is_null($message)) { 3118 # Return default error message 3119 return sprintf($this->default_error_messages[$key], $value); 3120 } else { 3121 # Return your custom error message 3122 return $message; 3123 } 3124 } 3125 3126 /** 3127 * Test whether argument is a PEAR Error object or a MDB2 Error object. 3128 * 3129 * @param object $obj Object to test 3130 * @return boolean Whether object is one of these two errors 3131 */ 3132 function is_error($obj) { 3133 if((PEAR::isError($obj)) || (MDB2::isError($obj))) { 3134 return true; 3135 } else { 3136 return false; 3137 } 3138 } 3139 3140 /** 3141 * Throw an exception describing an error in this object 3142 * 3143 * @throws {@link ActiveRecordError} 3144 */ 3145 function raise($message) { 3146 $error_message = "Model Class: ".$this->get_class_name()."<br>"; 3147 $error_message .= "Error Message: ".$message; 3148 throw new ActiveRecordError($error_message, "ActiveRecord Error", "500"); 3149 } 3150 3151 /** 3152 * Add or overwrite description of an error to the list of errors 3153 * @param string $error Error message text 3154 * @param string $key Key to associate with the error (in the 3155 * simple case, column name). If omitted, numeric keys will be 3156 * assigned starting with 0. If specified and the key already 3157 * exists in $errors, the old error message will be overwritten 3158 * with the value of $error. 3159 * @uses $errors 3160 */ 3161 function add_error($error, $key = null) { 3162 if(!is_null($key)) { 3163 $this->errors[$key] = $error; 3164 } else { 3165 $this->errors[] = $error; 3166 } 3167 } 3168 3169 /** 3170 * Return description of non-fatal errors 3171 * 3172 * @uses $errors 3173 * @param boolean $return_string 3174 * <ul> 3175 * <li>true => Concatenate all error descriptions into a string 3176 * using $seperator between elements and return the 3177 * string</li> 3178 * <li>false => Return the error descriptions as an array</li> 3179 * </ul> 3180 * @param string $seperator String to concatenate between error 3181 * descriptions if $return_string == true 3182 * @return mixed Error description(s), if any 3183 */ 3184 function get_errors($return_string = false, $seperator = "<br>") { 3185 if($return_string && count($this->errors) > 0) { 3186 return implode($seperator, $this->errors); 3187 } else { 3188 return $this->errors; 3189 } 3190 } 3191 3192 /** 3193 * Return errors as a string. 3194 * 3195 * Concatenate all error descriptions into a stringusing 3196 * $seperator between elements and return the string. 3197 * @param string $seperator String to concatenate between error 3198 * descriptions 3199 * @return string Concatenated error description(s), if any 3200 */ 3201 function get_errors_as_string($seperator = "<br>") { 3202 return $this->get_errors(true, $seperator); 3203 } 2783 3204 2784 3205 /** … … 2795 3216 2796 3217 } 3218 3219 2797 3220 2798 3221 // -- set Emacs parameters --
