├── .gitignore ├── tests ├── support │ ├── models │ │ ├── record_model.php │ │ ├── soft_delete_model.php │ │ ├── protected_attributes_model.php │ │ ├── validated_model.php │ │ ├── serialised_data_model.php │ │ ├── relationship_model.php │ │ ├── callback_parameter_model.php │ │ ├── after_callback_model.php │ │ └── before_callback_model.php │ ├── database.php │ └── test_helper.php └── MY_Model_test.php ├── composer.json ├── .travis.yml ├── phpunit.xml ├── LICENSE ├── core ├── MY_Tree_Model.php └── MY_Model.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | composer.lock 4 | composer.phar -------------------------------------------------------------------------------- /tests/support/models/record_model.php: -------------------------------------------------------------------------------- 1 | =0.7.2" 5 | }, 6 | "autoload": { 7 | "classmap": [ "core", "tests/support/models" ] 8 | } 9 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | 6 | before_script: 7 | - curl -s https://getcomposer.org/installer | php 8 | - php composer.phar install --dev 9 | 10 | script: vendor/bin/phpunit tests/MY_Model_test.php -------------------------------------------------------------------------------- /tests/support/models/soft_delete_model.php: -------------------------------------------------------------------------------- 1 | soft_delete_key = $key; 12 | } 13 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | ./tests/MY_Model_test.php 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/support/models/protected_attributes_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * protected_attributes_model.php contains a test model with protected attributes 12 | */ 13 | 14 | class Protected_attributes_model extends MY_Model 15 | { 16 | public $protected_attributes = array( 'id', 'hash' ); 17 | } -------------------------------------------------------------------------------- /tests/support/models/validated_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class Validated_model extends MY_Model 11 | { 12 | public $validate = array( 13 | array( 'field' => 'name', 'label' => 'Name', 'rules' => 'required' ), 14 | array( 'field' => 'sexyness', 'label' => 'Sexyness', 'rules' => 'required' ) 15 | ); 16 | } -------------------------------------------------------------------------------- /tests/support/models/serialised_data_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * serialised_data_model.php contains a test model that includes serialising a columns 12 | */ 13 | 14 | class Serialised_data_model extends MY_Model 15 | { 16 | public $before_create = array( 'serialize(data)' ); 17 | public $before_update = array( 'serialize(data)' ); 18 | } -------------------------------------------------------------------------------- /tests/support/models/relationship_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * relationship_model.php contains a test model that has a belongs_to and has_many relationship 12 | */ 13 | 14 | class Belongs_to_model extends MY_Model 15 | { 16 | public $belongs_to = array( 'author' ); 17 | public $has_many = array( 'comments' ); 18 | } 19 | 20 | class Author_model extends MY_Model 21 | { 22 | protected $_table = 'authors'; 23 | } -------------------------------------------------------------------------------- /tests/support/models/callback_parameter_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * callback_parameter_model.php contains a test model that defines a callback 12 | * with embedded parameters 13 | */ 14 | 15 | class Callback_parameter_model extends MY_Model 16 | { 17 | public $callback = array('some_callback(some_param,another_param)'); 18 | 19 | public function some_method() 20 | { 21 | $this->trigger('callback'); 22 | } 23 | 24 | protected function some_callback() 25 | { 26 | throw new Callback_Test_Exception($this->callback_parameters); 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jamie Rumbelow 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/support/database.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * database.php is a fakeified CodeIgniter query builder 12 | */ 13 | 14 | class MY_Model_Mock_DB 15 | { 16 | /** 17 | * CI_DB 18 | */ 19 | public function select() { } 20 | public function where() { } 21 | public function where_in() { } 22 | public function get() { } 23 | public function from() { } 24 | public function insert() { } 25 | public function insert_id() { } 26 | public function set() { } 27 | public function update() { } 28 | public function delete() { } 29 | public function order_by() { } 30 | public function limit() { } 31 | public function count_all_results() { } 32 | public function count_all() { } 33 | public function truncate() { } 34 | 35 | /** 36 | * CI_DB_Result 37 | */ 38 | public function row() { } 39 | public function result() { } 40 | public function row_array() { } 41 | public function result_array() { } 42 | } -------------------------------------------------------------------------------- /tests/support/models/after_callback_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * after_callback_model.php contains a test model that defines every after callback as a function 12 | * that throws an exception. We can then catch that in the tests to ensure callbacks work. 13 | */ 14 | 15 | class After_callback_model extends MY_Model 16 | { 17 | protected $after_create = array('test_throw'); 18 | protected $after_update = array('test_throw'); 19 | protected $after_get = array('test_data_callback', 'test_data_callback_two'); 20 | protected $after_delete = array('test_throw'); 21 | 22 | protected function test_throw($row) 23 | { 24 | throw new Callback_Test_Exception($row); 25 | } 26 | 27 | protected function test_data_callback($row) 28 | { 29 | $row['key'] = 'Value'; 30 | return $row; 31 | } 32 | 33 | protected function test_data_callback_two($row) 34 | { 35 | $row['another_key'] = '123 Value'; 36 | return $row; 37 | } 38 | } -------------------------------------------------------------------------------- /tests/support/models/before_callback_model.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * before_callback_model.php contains a test model that defines every before callback as a function 12 | * that throws an exception. We can then catch that in the tests to ensure callbacks work. 13 | */ 14 | 15 | class Before_callback_model extends MY_Model 16 | { 17 | protected $before_create = array('test_data_callback', 'test_data_callback_two'); 18 | protected $before_update = array('test_data_callback', 'test_data_callback_two'); 19 | protected $before_get = array('test_throw'); 20 | protected $before_delete = array('test_throw'); 21 | 22 | protected function test_throw($row) 23 | { 24 | throw new Callback_Test_Exception($row); 25 | } 26 | 27 | protected function test_data_callback($row) 28 | { 29 | $row['key'] = 'Value'; 30 | return $row; 31 | } 32 | 33 | protected function test_data_callback_two($row) 34 | { 35 | $row['another_key'] = '123 Value'; 36 | return $row; 37 | } 38 | } -------------------------------------------------------------------------------- /tests/support/test_helper.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * test_helper.php is the bootstrap file for our tests - it loads up an 12 | * appropriate faux-CodeIgniter environment for our tests to run in. 13 | */ 14 | 15 | // Load our MY_Model and the fakeish record model 16 | require_once 'vendor/autoload.php'; 17 | 18 | require_once 'tests/support/database.php'; 19 | 20 | 21 | /** 22 | * Fake the CodeIgniter base model! 23 | */ 24 | class CI_Model 25 | { 26 | public function __construct() 27 | { 28 | $this->load = new CI_Loader(); 29 | 30 | // Pretend CI has a loaded DB already. 31 | $this->db = new MY_Model_Mock_DB(); 32 | } 33 | } 34 | 35 | /** 36 | * The loads happen in the constructor (before we can mock anything out), 37 | * so instead we'll fakeify the Loader 38 | */ 39 | class CI_Loader 40 | { 41 | public function __call($method, $params = array()) {} 42 | } 43 | 44 | /** 45 | * ...but relationships load models, so fake that 46 | */ 47 | class MY_Model_Mock_Loader 48 | { 49 | public function model($name, $assigned_name = '') { } 50 | } 51 | 52 | /** 53 | * We also need to fake the inflector 54 | */ 55 | function singular($name) 56 | { 57 | return 'comment'; 58 | } 59 | 60 | function plural($name) 61 | { 62 | return 'records'; 63 | } 64 | 65 | /** 66 | * Let our tests know about our callbacks 67 | */ 68 | 69 | class MY_Model_Test_Exception extends Exception 70 | { 71 | public $passed_object = FALSE; 72 | 73 | public function __construct($passed_object, $message = '') 74 | { 75 | parent::__construct($message); 76 | $this->passed_object = $passed_object; 77 | } 78 | } 79 | 80 | class Callback_Test_Exception extends MY_Model_Test_Exception 81 | { 82 | public function __construct($passed_object) 83 | { 84 | parent::__construct($passed_object, 'Callback is being successfully thrown'); 85 | } 86 | } -------------------------------------------------------------------------------- /core/MY_Tree_Model.php: -------------------------------------------------------------------------------- 1 | , 2014-2022 5 | * @license The MIT License, http://opensource.org/licenses/MIT 6 | * 7 | * Code repository: https://github.com/ivantcholakov/codeigniter-base-model 8 | * 9 | * This is experimental and undocumented code. Examine and understand it before use. 10 | * 11 | * This model is intended to serve hierarhical structure within the table - 12 | * hierarhical set of pages, categories of products, etc., with internal 13 | * relationship id - parent_id (tree-like logical structure). 14 | * 15 | * A quick example that tests everithing (create Pages model that extends MY_Tree_Model): 16 | * 17 | * // Converting and dumping the tree structure as a list. 18 | * $menu = $this->pages->get_list( 19 | * null, // No root id specified, dump all. 20 | * 'name, slug, display_order', // Which fields to select. 21 | * array(array('display_on_menu', 1), array('left_menu', 1)), // Where conditions. 22 | * array('display_order', 'asc') // Order. 23 | * ); 24 | * var_dump($menu); // The result rows are arrays, always. 25 | */ 26 | 27 | // If your system uses class autoloading feature, 28 | // then the following require statement would not be needed. 29 | if (!class_exists('MY_Model', false)) { 30 | require dirname(__FILE__).'/MY_Model.php'; 31 | } 32 | 33 | class MY_Tree_Model extends MY_Model { 34 | 35 | protected $parent_id_key = 'parent_id'; 36 | protected $level_index = 'level'; 37 | protected $children_index = 'children'; 38 | protected $has_children_index = 'has_children'; 39 | protected $children_count_index = 'children_count'; 40 | protected $display_level_index = 'display_level'; 41 | 42 | protected $tree_list_total_count = 0; 43 | 44 | protected $cache = null; 45 | 46 | public function __construct() { 47 | 48 | parent::__construct(); 49 | 50 | $this->clear_cache(); 51 | } 52 | 53 | public function clear_cache() { 54 | 55 | $this->cache = array(); 56 | } 57 | 58 | public function get_parent($id) { 59 | 60 | $id = (int) $id; 61 | 62 | if (isset($this->cache[$id])) { 63 | 64 | if (array_key_exists('parent_id', $this->cache[$id])) { 65 | return $this->cache[$id]['parent_id']; 66 | } 67 | 68 | } else { 69 | 70 | $this->cache[$id] = array(); 71 | } 72 | 73 | $db = clone $this; 74 | $db->reset_query(); 75 | 76 | $parent_id = (int) $db->select($this->parent_id_key)->as_value()->get($id); 77 | $this->cache[$id]['parent_id'] = $parent_id; 78 | 79 | return $parent_id; 80 | } 81 | 82 | public function get_parents($id) { 83 | 84 | $id = (int) $id; 85 | 86 | $result = array(); 87 | 88 | while (!empty($id)) { 89 | 90 | $id = $this->get_parent($id); 91 | 92 | if (!empty($id)) { 93 | $result[] = $id; 94 | } 95 | } 96 | 97 | return $result; 98 | } 99 | 100 | public function get_level($id) { 101 | 102 | $id = (int) $id; 103 | 104 | if (isset($this->cache[$id])) { 105 | 106 | if (array_key_exists('level', $this->cache[$id])) { 107 | return $this->cache[$id]['level']; 108 | } 109 | 110 | } else { 111 | 112 | $this->cache[$id] = array(); 113 | } 114 | 115 | if (!isset($this->cache[$id]['level'])) { 116 | 117 | $this->cache[$id]['level'] = 0; 118 | } 119 | 120 | $parent_id = $id; 121 | 122 | while (!empty($parent_id)) { 123 | 124 | $parent_id = $this->get_parent($parent_id); 125 | 126 | if (!empty($parent_id)) { 127 | $this->cache[$id]['level']++; 128 | } 129 | } 130 | 131 | return $this->cache[$id]['level']; 132 | } 133 | 134 | public function contains($id, $child_id) { 135 | 136 | $id = (int) $id; 137 | $child_id = (int) $child_id; 138 | 139 | if (empty($id) || empty($child_id)) { 140 | return false; 141 | } 142 | 143 | if ($id == $child_id) { 144 | return true; 145 | } 146 | 147 | $child_id = $this->get_parent($child_id); 148 | 149 | return $this->contains($id, $child_id); 150 | } 151 | 152 | public function contains_one($id, $children_ids) { 153 | 154 | $id = (int) $id; 155 | 156 | if (!is_array($children_ids)) { 157 | $children_ids = array($children_ids); 158 | } 159 | 160 | foreach ($children_ids as $child_id) { 161 | 162 | if ($this->contains($id, $child_id)) { 163 | return true; 164 | } 165 | } 166 | 167 | return false; 168 | } 169 | 170 | public function contains_all($id, $children_ids) { 171 | 172 | $id = (int) $id; 173 | 174 | if (!is_array($children_ids)) { 175 | $children_ids = array($children_ids); 176 | } 177 | 178 | foreach ($children_ids as $child_id) { 179 | 180 | if (!$this->contains($id, $child_id)) { 181 | return false; 182 | } 183 | } 184 | 185 | return true; 186 | } 187 | 188 | public function has_children($id) { 189 | 190 | $id = (int) $id; 191 | 192 | if (isset($this->cache[$id])) { 193 | 194 | if (array_key_exists('has_children', $this->cache[$id])) { 195 | return $this->cache[$id]['has_children']; 196 | } 197 | 198 | } else { 199 | 200 | $this->cache[$id] = array(); 201 | } 202 | 203 | $db = clone $this; 204 | $db->reset_query(); 205 | 206 | $has_children = !is_null($db->select($this->primary_key)->as_value()->first($this->parent_id_key, $id)); 207 | $this->cache[$id]['has_children'] = $has_children; 208 | 209 | return $has_children; 210 | } 211 | 212 | public function children_count($id) { 213 | 214 | $id = (int) $id; 215 | 216 | if (isset($this->cache[$id])) { 217 | 218 | if (array_key_exists('children_count', $this->cache[$id])) { 219 | return $this->cache[$id]['children_count']; 220 | } 221 | 222 | } else { 223 | 224 | $this->cache[$id] = array(); 225 | } 226 | 227 | $db = clone $this; 228 | $db->reset_query(); 229 | 230 | $children_count = (int) $db->select('COUNT('.$this->protect_identifiers($this->primary_key).')')->as_value()->first($this->parent_id_key, $id); 231 | $this->cache[$id]['children_count'] = $children_count; 232 | 233 | return $children_count; 234 | } 235 | 236 | public function get_children($id = null, $select = '', $where = array(), $order_by = array(), $depth = null) { 237 | 238 | $id = (int) $id; 239 | 240 | if (empty($id)) { 241 | 242 | $level = 0; 243 | 244 | } else { 245 | 246 | $level = $this->get_level($id); 247 | $level++; 248 | } 249 | 250 | if ($depth !== null) { 251 | 252 | $depth = (int) $depth; 253 | 254 | if ($depth < $level) { 255 | return null; 256 | } 257 | } 258 | 259 | if (!is_array($select)) { 260 | 261 | $select = trim((string) $select); 262 | $select = $select == '' ? $this->_table.'.*' : $select; 263 | $select = explode(',', $select); 264 | 265 | } elseif (empty($select)) { 266 | 267 | $select = array($this->_table.'.*'); 268 | } 269 | 270 | $select = array_merge( 271 | array( 272 | $this->_table.'.'.$this->primary_key, 273 | $this->_table.'.'.$this->parent_id_key, 274 | "$level AS {$this->level_index}" 275 | ), 276 | $select 277 | ); 278 | 279 | $db = clone $this; 280 | 281 | $db->offset(0); 282 | $db->limit(PHP_INT_MAX); 283 | 284 | $db->select($select); 285 | 286 | if (is_array($where) && !empty($where)) { 287 | 288 | if ($this->_is_multidimensional_array($where)) { 289 | 290 | foreach ($where as $w) { 291 | 292 | switch (count($w)) { 293 | 294 | case 1: 295 | $db->where($w[0]); 296 | break; 297 | 298 | case 2: 299 | if (is_array($w[1])) { 300 | $db->where_in($w[0], $w[1]); 301 | } else { 302 | $db->where($w[0], $w[1]); 303 | } 304 | break; 305 | 306 | case 3: 307 | if (is_array($w[1])) { 308 | $db->where_in($w[0], $w[1], $w[2]); 309 | } else { 310 | $db->where($w[0], $w[1], $w[2]); 311 | } 312 | break; 313 | } 314 | } 315 | 316 | } else { 317 | 318 | switch (count($where)) { 319 | 320 | case 1: 321 | $db->where($where[0]); 322 | break; 323 | 324 | case 2: 325 | if (is_array($where[1])) { 326 | $db->where_in($where[0], $where[1]); 327 | } else { 328 | $db->where($where[0], $where[1]); 329 | } 330 | break; 331 | 332 | case 3: 333 | if (is_array($where[1])) { 334 | $db->where_in($where[0], $where[1], $where[2]); 335 | } else { 336 | $db->where($where[0], $where[1], $where[2]); 337 | } 338 | break; 339 | } 340 | } 341 | } 342 | 343 | if (is_array($order_by) && !empty($order_by)) { 344 | 345 | if ($this->_is_multidimensional_array($order_by)) { 346 | 347 | foreach ($order_by as $o) { 348 | 349 | switch (count($o)) { 350 | 351 | case 1: 352 | $db->order_by($o[0]); 353 | break; 354 | 355 | case 2: 356 | $db->order_by($o[0], $o[1]); 357 | break; 358 | 359 | case 3: 360 | $db->order_by($o[0], $o[1], $o[2]); 361 | break; 362 | } 363 | } 364 | 365 | } else { 366 | 367 | switch (count($order_by)) { 368 | 369 | case 1: 370 | $db->order_by($order_by[0]); 371 | break; 372 | 373 | case 2: 374 | $db->order_by($order_by[0], $order_by[1]); 375 | break; 376 | 377 | case 3: 378 | $db->order_by($order_by[0], $order_by[1], $order_by[2]); 379 | break; 380 | } 381 | } 382 | } 383 | 384 | if (empty($id)) { 385 | 386 | return $db 387 | ->group_start() 388 | ->where($this->_table.'.'.$this->parent_id_key, 0) 389 | ->or_where($this->_table.'.'.$this->parent_id_key, null) 390 | ->group_end() 391 | ->as_array() 392 | ->find(); 393 | } 394 | 395 | return $db 396 | ->where($this->_table.'.'.$this->parent_id_key, $id) 397 | ->as_array() 398 | ->find(); 399 | } 400 | 401 | public function get_tree($id = null, $select = '', $where = array(), $order_by = array(), $depth = null) { 402 | 403 | $id = (int) $id; 404 | 405 | $cloned = clone($this); 406 | $result = $cloned->get_children($id, $select, $where, $order_by, $depth); 407 | 408 | if (!empty($result)) { 409 | 410 | foreach ($result as $key => $row) { 411 | 412 | $cloned = clone($this); 413 | $children = $cloned->get_tree((int) $row[$this->primary_key], $select, $where, $order_by, $depth); 414 | 415 | if (!empty($children)) { 416 | 417 | $result[$key][$this->children_index] = $children; 418 | $result[$key][$this->has_children_index] = true; 419 | $result[$key][$this->children_count_index] = count($children); 420 | 421 | } else { 422 | 423 | $result[$key][$this->has_children_index] = false; 424 | $result[$key][$this->children_count_index] = 0; 425 | } 426 | } 427 | 428 | $this->reset_query(); 429 | 430 | return $result; 431 | } 432 | 433 | $this->reset_query(); 434 | 435 | return null; 436 | } 437 | 438 | public function get_tree_list($id = null, $select = '', $where = array(), $order_by = array(), $depth = null) { 439 | 440 | // This code is not effective way for large results. 441 | 442 | $offset = $this->get_offset(); 443 | $limit = $this->get_limit(); 444 | 445 | $this->tree_list_total_count = 0; 446 | 447 | $result = $this->get_tree($id, $select, $where, $order_by, $depth); 448 | $result = $this->_tree_to_list($result); 449 | 450 | $this->tree_list_total_count = count($result); 451 | 452 | if ($offset !== false || $limit !== false) { 453 | 454 | $offset = (int) $offset; 455 | 456 | if ($limit === false) { 457 | return array_slice($result, $offset); 458 | } 459 | 460 | return array_slice($result, $offset, $limit); 461 | } 462 | 463 | return $result; 464 | } 465 | 466 | public function get_last_tree_list_total_count() { 467 | 468 | return $this->tree_list_total_count; 469 | } 470 | 471 | /** 472 | * @deprecated 473 | */ 474 | public function get_list($id = null, $select = '', $where = array(), $order_by = array(), $depth = null) { 475 | 476 | return $this->get_tree_list($id, $select, $where, $order_by, $depth); 477 | } 478 | 479 | protected function _tree_to_list(& $tree, $display_level = 0) { 480 | 481 | $result = array(); 482 | 483 | if (!empty($tree)) { 484 | 485 | foreach ($tree as $row) { 486 | 487 | $item = $row; 488 | 489 | if (isset($item[$this->children_index])) { 490 | unset($item[$this->children_index]); 491 | } 492 | 493 | $item[$this->display_level_index] = $display_level; 494 | 495 | $result[] = $item; 496 | 497 | if (!empty($row[$this->children_index])) { 498 | $result = array_merge($result, $this->_tree_to_list($row[$this->children_index], $display_level + 1)); 499 | } 500 | } 501 | } 502 | 503 | return $result; 504 | } 505 | 506 | protected function _is_multidimensional_array($array) { 507 | 508 | foreach ($array as $element) { 509 | 510 | if (!is_array($element)) { 511 | 512 | return false; 513 | } 514 | } 515 | 516 | return true; 517 | } 518 | 519 | public function get_path($id, $select = '') { 520 | 521 | $result = array(); 522 | 523 | $id = (int) $id; 524 | 525 | if (empty($id)) { 526 | return $result; 527 | } 528 | 529 | if (!is_array($select)) { 530 | 531 | $select = trim((string) $select); 532 | $select = $select == '' ? $this->_table.'.*' : $select; 533 | $select = explode(',', $select); 534 | 535 | } elseif (empty($select)) { 536 | 537 | $select = array($this->_table.'.*'); 538 | } 539 | 540 | $select = array_merge( 541 | array( 542 | $this->primary_key, 543 | $this->parent_id_key, 544 | ), 545 | $select 546 | ); 547 | 548 | $item = $this->select($select)->as_array()->get($id); 549 | 550 | if (empty($item)) { 551 | return $result; 552 | } 553 | 554 | $result[] = $item; 555 | 556 | $parent_id = (int) $item[$this->parent_id_key]; 557 | 558 | while (!empty($parent_id)) { 559 | 560 | $item = $this->select($select)->as_array()->get($parent_id); 561 | 562 | if (empty($item)) { 563 | break; 564 | } 565 | 566 | $result[] = $item; 567 | 568 | $parent_id = $this->get_parent($parent_id); 569 | } 570 | 571 | return array_reverse($result); 572 | } 573 | 574 | } 575 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | codeigniter-base-model 3 | ===================================== 4 | 5 | Additional Features by Ivan Tcholakov, 2012-2022. 6 | -------------------------------------------------- 7 | 8 | **First, an important note by Ivan Tcholakov:** I hate writing tests, this is why: http://www.joelonsoftware.com/items/2009/09/23.html . The purpose of this repository is for keeping some new ad-hoc introduced features, which I use in my projects. I recommend you to go to the original repository of Jamie Rumbelow, https://github.com/jamierumbelow/codeigniter-base-model . 9 | 10 | **BEHAVIOR CHANGES** 11 | * When an instance that is derived from MY_Model is clonned, the internal query builder instance is clonned too. 12 | * The clause LIMIT 1 has been added within the methods get(), get_by(), update(), update_by(), delete(), delete_by(). It should work with MySQL at least. This is for prevention targeting more than one records by an accident. 13 | * The internal method _set_where() accepts an empty WHERE parameter (NULL or an empty string). So *_by() methods may be injected preliminary with complex WHERE clauses this way: 14 | 15 | ```php 16 | $this->load->model('products'); 17 | 18 | $this->products 19 | ->where('out_of_stock', 0) 20 | ->group_start() // As of CI 3.0.0 21 | ->like('name', 'sandals') 22 | ->or_like('description', 'sandals') 23 | ->group_end() // As of CI 3.0.0 24 | ; // This is our complex WHERE clause. 25 | // It is to be used by the next statement. 26 | 27 | $search_list = $this->products->get_many_by(); // get_many_by() without parameters, find() is a newer preferable alias. 28 | 29 | var_dump($search_list); 30 | 31 | // This was the "hackish" way. See below for an improved version of this example. 32 | ``` 33 | * The methods insert(), insert_many(), update(), update_many(), update_by(), update_many_by(), update_all() accept additional boolean parameter $escape. Use it wisely. An example: 34 | 35 | ```php 36 | if ( ! $this->agent->is_robot()) 37 | { 38 | $this->products->update($id, array('preview_counter' => 'preview_counter + 1'), FALSE, FALSE); 39 | } 40 | ``` 41 | 42 | * The method order_by() accepts third parameter $escape which should work as of CI 3.0.0. 43 | * The methods update(), update_many(), update_by(), update_many_by(), update_all(), count_by(), count_all(), exists() are to respect soft deletion. 44 | * New methods: first() - an alias of get_by(), and find() - an alias of get_many_by(). 45 | 46 | **CRUD INTERFACE** 47 | * New methods update_many_by() and delete_many_by() have been added. 48 | 49 | **UTILITY METHODS** 50 | * The method exists($primary_value) has been added. Sample usage: 51 | 52 | ```php 53 | load->model('products'); 63 | 64 | // URL: http://my-site.com/product/124 - 124 is our id to be validated. 65 | $this->id = $this->_validate_product_id($this->uri->rsegment(3)); 66 | } 67 | 68 | public function index() 69 | { 70 | $product = $this->products->get($this->id); 71 | 72 | // Show product data. 73 | $this->load->view('product', $product); 74 | } 75 | 76 | // Other methods 77 | // ... 78 | 79 | protected function _validate_product_id($id) 80 | { 81 | $id = (int) $id; 82 | 83 | if ( 84 | empty($id) 85 | || 86 | !$this->products->exists($id) // Here we use our method exists(). 87 | ) { 88 | if ($this->input->is_ajax_request()) 89 | { 90 | exit; 91 | } 92 | show_404(); 93 | } 94 | 95 | return $id; 96 | } 97 | 98 | } 99 | ``` 100 | 101 | * The wrapper methods list_fields(), field_exists($field_name) and field_data() have been added. 102 | * The database() getter method has been added. 103 | * The fields() method has been added. It returns an array witn names of the existing fields within the tables, also it caches its result for avoiding multiple database queries. 104 | * The get_empty() method has been added. It returns an empty record with NULL values. Respects 'after_get' observers. 105 | * The primary_key() getter method has been added. 106 | * The wrapper method table_exists() has been added. 107 | * The wrapper method reset_query() has been added (for CodeIgniter 3). 108 | * The method count_all_results() has been added as an alias of count_all(). 109 | 110 | **GLOBAL SCOPES** 111 | * A new method as_value() has been added. By using it (with get() and get_by() methods only) retrieving single values gets easy. An example: 112 | 113 | ```php 114 | $this->load->model('categories'); 115 | 116 | $id = 10; 117 | 118 | $parent_id = $this->categories 119 | ->select('parent_id') 120 | ->as_value() 121 | ->get($id); // NULL is returned if the containing record is not found. 122 | ``` 123 | 124 | Note: Also, a special method value() has been added for serving cases like this one: 125 | 126 | ```php 127 | // The following expression works, but it is quite verbose (username and email are assumed as unique): 128 | $user_id = $this->users->select('id')->where('user_id', $user_id)->or_where('email', $email)->as_value()->first(); 129 | 130 | // This is the simpler way: 131 | $user_id = $this->users->where('username', $username)->or_where('email', $email)->value('id'); 132 | ``` 133 | 134 | * A new method as_sql() has been added. It forces get*(), insert*(), update*(), delete*() and some other methods to return compiled SQL as a string (or an array of strings). This result modifier is convenient for debugging purposes. An example: 135 | 136 | ```php 137 | $this->load->model('products'); 138 | 139 | $result = $this->products 140 | ->as_sql() 141 | ->select('id, image, slug, category_id, name, promotext') 142 | ->distinct() 143 | ->where('is_promotion', 1) 144 | ->where('out_of_stock', 0) 145 | ->order_by('promotext', 'desc') 146 | ->order_by('category_id', 'asc') 147 | ->get_many_by(); 148 | 149 | var_dump($result); 150 | 151 | /* The result is the following SQL statement: 152 | SELECT DISTINCT `id`, `image`, `slug`, `category_id`, `name`, `promotext` 153 | FROM `products` 154 | WHERE `is_promotion` = 1 155 | AND `out_of_stock` =0 156 | ORDER BY `promotext` DESC, `category_id` ASC 157 | */ 158 | ``` 159 | 160 | * A new method as_json() has been added, it enforces JSON presentation of the returned result. 161 | * A new method skip_observers() has been added, it disables triggering of all the attached/registered observers. 162 | 163 | **QUERY BUILDER DIRECT ACCESS METHODS** 164 | * The method select($select = '*', $escape = NULL) has been added. An example: 165 | 166 | ```php 167 | $this->load->model('products'); 168 | // Only the needed coulums are retrieved. 169 | $product_list = $this->products->select('id, name, image')->get_all(); 170 | var_dump($product_list); 171 | ``` 172 | 173 | * The methods escape(), escape_like_str() and escape_str() have been added. 174 | * More have been added: offset(), where(), or_where(), where_in(), or_where_in(), where_not_in(), or_where_not_in(), like(), not_like(), or_like(), or_not_like(), group_start(), or_group_start(), not_group_start(), or_not_group_start(), group_end(), group_by(), having(), or_having(). Thus, one of the exmples shown above gets simplified: 175 | 176 | ```php 177 | $this->load->model('products'); 178 | 179 | $search_list = $this->products 180 | ->where('out_of_stock', 0) 181 | ->group_start() // Works on CI 3.0.0 182 | ->like('name', 'sandals') 183 | ->or_like('description', 'sandals') 184 | ->group_end() // Works on CI 3.0.0 185 | ->get_many_by() // or ->find() 186 | ; 187 | // SELECT * FROM `products` WHERE `out_of_stock` =0 AND ( `name` LIKE '%sandals%' ESCAPE '!' OR `description` LIKE '%sandals%' ESCAPE '!' ) 188 | 189 | var_dump($search_list); 190 | ``` 191 | 192 | * The method distinct() has been added. 193 | * The wrapper method join() has been added. 194 | 195 | **INPUT DATA FILTRATION BEFORE INSERT/UPDATE** 196 | * A new flag has been added that enforces removal of input data fields that don't exist within the table. An example: 197 | 198 | ```php 199 | class Pages extends MY_Model 200 | { 201 | 202 | protected $check_for_existing_fields = TRUE; // This flag enforces the input filtration. 203 | // Set it to TRUE for enabling the feature. 204 | public $protected_attributes = array('id'); 205 | 206 | protected $_table = 'pages'; 207 | 208 | ... 209 | } 210 | ``` 211 | 212 | After that, within a controller you may use data from a html form directly, all the extra-data from it will be ignored: 213 | 214 | ```php 215 | ... 216 | if ($this->form_validation->run()) 217 | { 218 | $id = (int) $this->input->post('id'); // TODO: Validate $id. It is not done here for simplicity. 219 | $this->pages->update($id, $this->input->post()); // Also note, that 'id' has been declared as a "protected attribute". 220 | 221 | // Set a confirmation message here. 222 | } 223 | else 224 | { 225 | // Set an error message here. 226 | } 227 | ... 228 | ``` 229 | 230 | **MORE OBSERVERS** 231 | * More built-in observers have been added: 'created_by', 'updated_by', 'deleted_at', and 'deleted_by'. 232 | 233 | The original README goes here... 234 | ------------------------------- 235 | 236 | codeigniter-base-model 237 | ===================================== 238 | 239 | [![Build Status](https://secure.travis-ci.org/jamierumbelow/codeigniter-base-model.png?branch=master)](http://travis-ci.org/jamierumbelow/codeigniter-base-model) 240 | 241 | My CodeIgniter Base Model is an extended CI_Model class to use in your CodeIgniter applications. It provides a full CRUD base to make developing database interactions easier and quicker, as well as an event-based observer system, in-model data validation, intelligent table name guessing and soft delete. 242 | 243 | Synopsis 244 | -------- 245 | 246 | ```php 247 | class Post_model extends MY_Model { } 248 | 249 | $this->load->model('post_model', 'post'); 250 | 251 | $this->post->get_all(); 252 | 253 | $this->post->get(1); 254 | $this->post->get_by('title', 'Pigs CAN Fly!'); 255 | $this->post->get_many_by('status', 'open'); 256 | 257 | $this->post->insert(array( 258 | 'status' => 'open', 259 | 'title' => "I'm too sexy for my shirt" 260 | )); 261 | 262 | $this->post->update(1, array( 'status' => 'closed' )); 263 | 264 | $this->post->delete(1); 265 | ``` 266 | 267 | Installation/Usage 268 | ------------------ 269 | 270 | Download and drag the MY\_Model.php file into your _application/core_ folder. CodeIgniter will load and initialise this class automatically for you. 271 | 272 | Extend your model classes from `MY_Model` and all the functionality will be baked in automatically. 273 | 274 | Naming Conventions 275 | ------------------ 276 | 277 | This class will try to guess the name of the table to use, by finding the plural of the class name. 278 | 279 | For instance: 280 | 281 | class Post_model extends MY_Model { } 282 | 283 | ...will guess a table name of `posts`. It also works with `_m`: 284 | 285 | class Book_m extends MY_Model { } 286 | 287 | ...will guess `books`. 288 | 289 | If you need to set it to something else, you can declare the _$\_table_ instance variable and set it to the table name: 290 | 291 | class Post_model extends MY_Model 292 | { 293 | public $_table = 'blogposts'; 294 | } 295 | 296 | Some of the CRUD functions also assume that your primary key ID column is called _'id'_. You can overwrite this functionality by setting the _$primary\_key_ instance variable: 297 | 298 | class Post_model extends MY_Model 299 | { 300 | public $primary_key = 'post_id'; 301 | } 302 | 303 | Callbacks/Observers 304 | ------------------- 305 | 306 | There are many times when you'll need to alter your model data before it's inserted or returned. This could be adding timestamps, pulling in relationships or deleting dependent rows. The MVC pattern states that these sorts of operations need to go in the model. In order to facilitate this, **MY_Model** contains a series of callbacks/observers -- methods that will be called at certain points. 307 | 308 | The full list of observers are as follows: 309 | 310 | * $before_create 311 | * $after_create 312 | * $before_update 313 | * $after_update 314 | * $before_get 315 | * $after_get 316 | * $before_delete 317 | * $after_delete 318 | 319 | These are instance variables usually defined at the class level. They are arrays of methods on this class to be called at certain points. An example: 320 | 321 | ```php 322 | class Book_model extends MY_Model 323 | { 324 | public $before_create = array( 'timestamps' ); 325 | 326 | protected function timestamps($book) 327 | { 328 | $book['created_at'] = $book['updated_at'] = date('Y-m-d H:i:s'); 329 | return $book; 330 | } 331 | } 332 | ``` 333 | 334 | **Remember to always always always return the `$row` object you're passed. Each observer overwrites its predecessor's data, sequentially, in the order the observers are defined.** 335 | 336 | Observers can also take parameters in their name, much like CodeIgniter's Form Validation library. Parameters are then accessed in `$this->callback_parameters`: 337 | 338 | public $before_create = array( 'data_process(name)' ); 339 | public $before_update = array( 'data_process(date)' ); 340 | 341 | protected function data_process($row) 342 | { 343 | $row[$this->callback_parameters[0]] = $this->_process($row[$this->callback_parameters[0]]); 344 | 345 | return $row; 346 | } 347 | 348 | Validation 349 | ---------- 350 | 351 | MY_Model uses CodeIgniter's built in form validation to validate data on insert. 352 | 353 | You can enable validation by setting the `$validate` instance to the usual form validation library rules array: 354 | 355 | class User_model extends MY_Model 356 | { 357 | public $validate = array( 358 | array( 'field' => 'email', 359 | 'label' => 'email', 360 | 'rules' => 'required|valid_email|is_unique[users.email]' ), 361 | array( 'field' => 'password', 362 | 'label' => 'password', 363 | 'rules' => 'required' ), 364 | array( 'field' => 'password_confirmation', 365 | 'label' => 'confirm password', 366 | 'rules' => 'required|matches[password]' ), 367 | ); 368 | } 369 | 370 | Anything valid in the form validation library can be used here. To find out more about the rules array, please [view the library's documentation](http://codeigniter.com/user_guide/libraries/form_validation.html#validationrulesasarray). 371 | 372 | With this array set, each call to `insert()` or `update()` will validate the data before allowing the query to be run. **Unlike the CodeIgniter validation library, this won't validate the POST data, rather, it validates the data passed directly through.** 373 | 374 | You can skip the validation with `skip_validation()`: 375 | 376 | $this->user_model->skip_validation(); 377 | $this->user_model->insert(array( 'email' => 'blah' )); 378 | 379 | Alternatively, pass through a `TRUE` to `insert()`: 380 | 381 | $this->user_model->insert(array( 'email' => 'blah' ), TRUE); 382 | 383 | Under the hood, this calls `validate()`. 384 | 385 | Protected Attributes 386 | -------------------- 387 | 388 | If you're lazy like me, you'll be grabbing the data from the form and throwing it straight into the model. While some of the pitfalls of this can be avoided with validation, it's a very dangerous way of entering data; any attribute on the model (any column in the table) could be modified, including the ID. 389 | 390 | To prevent this from happening, MY_Model supports protected attributes. These are columns of data that cannot be modified. 391 | 392 | You can set protected attributes with the `$protected_attributes` array: 393 | 394 | class Post_model extends MY_Model 395 | { 396 | public $protected_attributes = array( 'id', 'hash' ); 397 | } 398 | 399 | Now, when `insert` or `update` is called, the attributes will automatically be removed from the array, and, thus, protected: 400 | 401 | $this->post_model->insert(array( 402 | 'id' => 2, 403 | 'hash' => 'aqe3fwrga23fw243fWE', 404 | 'title' => 'A new post' 405 | )); 406 | 407 | // SQL: INSERT INTO posts (title) VALUES ('A new post') 408 | 409 | Relationships 410 | ------------- 411 | 412 | **MY\_Model** now has support for basic _belongs\_to_ and has\_many relationships. These relationships are easy to define: 413 | 414 | class Post_model extends MY_Model 415 | { 416 | public $belongs_to = array( 'author' ); 417 | public $has_many = array( 'comments' ); 418 | } 419 | 420 | It will assume that a MY_Model API-compatible model with the singular relationship's name has been defined. By default, this will be `relationship_model`. The above example, for instance, would require two other models: 421 | 422 | class Author_model extends MY_Model { } 423 | class Comment_model extends MY_Model { } 424 | 425 | If you'd like to customise this, you can pass through the model name as a parameter: 426 | 427 | class Post_model extends MY_Model 428 | { 429 | public $belongs_to = array( 'author' => array( 'model' => 'author_m' ) ); 430 | public $has_many = array( 'comments' => array( 'model' => 'model_comments' ) ); 431 | } 432 | 433 | You can then access your related data using the `with()` method: 434 | 435 | $post = $this->post_model->with('author') 436 | ->with('comments') 437 | ->get(1); 438 | 439 | The related data will be embedded in the returned value from `get`: 440 | 441 | echo $post->author->name; 442 | 443 | foreach ($post->comments as $comment) 444 | { 445 | echo $message; 446 | } 447 | 448 | Separate queries will be run to select the data, so where performance is important, a separate JOIN and SELECT call is recommended. 449 | 450 | The primary key can also be configured. For _belongs\_to_ calls, the related key is on the current object, not the foreign one. Pseudocode: 451 | 452 | SELECT * FROM authors WHERE id = $post->author_id 453 | 454 | ...and for a _has\_many_ call: 455 | 456 | SELECT * FROM comments WHERE post_id = $post->id 457 | 458 | To change this, use the `primary_key` value when configuring: 459 | 460 | class Post_model extends MY_Model 461 | { 462 | public $belongs_to = array( 'author' => array( 'primary_key' => 'post_author_id' ) ); 463 | public $has_many = array( 'comments' => array( 'primary_key' => 'parent_post_id' ) ); 464 | } 465 | 466 | Arrays vs Objects 467 | ----------------- 468 | 469 | By default, MY_Model is setup to return objects using CodeIgniter's QB's `row()` and `result()` methods. If you'd like to use their array counterparts, there are a couple of ways of customising the model. 470 | 471 | If you'd like all your calls to use the array methods, you can set the `$return_type` variable to `array`. 472 | 473 | class Book_model extends MY_Model 474 | { 475 | protected $return_type = 'array'; 476 | } 477 | 478 | If you'd like just your _next_ call to return a specific type, there are two scoping methods you can use: 479 | 480 | $this->book_model->as_array() 481 | ->get(1); 482 | $this->book_model->as_object() 483 | ->get_by('column', 'value'); 484 | 485 | Soft Delete 486 | ----------- 487 | 488 | By default, the delete mechanism works with an SQL `DELETE` statement. However, you might not want to destroy the data, you might instead want to perform a 'soft delete'. 489 | 490 | If you enable soft deleting, the deleted row will be marked as `deleted` rather than actually being removed from the database. 491 | 492 | Take, for example, a `Book_model`: 493 | 494 | class Book_model extends MY_Model { } 495 | 496 | We can enable soft delete by setting the `$this->soft_delete` key: 497 | 498 | class Book_model extends MY_Model 499 | { 500 | protected $soft_delete = TRUE; 501 | } 502 | 503 | By default, MY_Model expects a `TINYINT` or `INT` column named `deleted`. If you'd like to customise this, you can set `$soft_delete_key`: 504 | 505 | class Book_model extends MY_Model 506 | { 507 | protected $soft_delete = TRUE; 508 | protected $soft_delete_key = 'book_deleted_status'; 509 | } 510 | 511 | Now, when you make a call to any of the `get_` methods, a constraint will be added to not withdraw deleted columns: 512 | 513 | => $this->book_model->get_by('user_id', 1); 514 | -> SELECT * FROM books WHERE user_id = 1 AND deleted = 0 515 | 516 | If you'd like to include deleted columns, you can use the `with_deleted()` scope: 517 | 518 | => $this->book_model->with_deleted()->get_by('user_id', 1); 519 | -> SELECT * FROM books WHERE user_id = 1 520 | 521 | If you'd like to include only the columns that have been deleted, you can use the `only_deleted()` scope: 522 | 523 | => $this->book_model->only_deleted()->get_by('user_id', 1); 524 | -> SELECT * FROM books WHERE user_id = 1 AND deleted = 1 525 | 526 | Built-in Observers 527 | ------------------- 528 | 529 | **MY_Model** contains a few built-in observers for things I've found I've added to most of my models. 530 | 531 | The timestamps (MySQL compatible) `created_at` and `updated_at` are now available as built-in observers: 532 | 533 | class Post_model extends MY_Model 534 | { 535 | public $before_create = array( 'created_at', 'updated_at' ); 536 | public $before_update = array( 'updated_at' ); 537 | } 538 | 539 | **MY_Model** also contains serialisation observers for serialising and unserialising native PHP objects. This allows you to pass complex structures like arrays and objects into rows and have it be serialised automatically in the background. Call the `serialize` and `unserialize` observers with the column name(s) as a parameter: 540 | 541 | class Event_model extends MY_Model 542 | { 543 | public $before_create = array( 'serialize(seat_types)' ); 544 | public $before_update = array( 'serialize(seat_types)' ); 545 | public $after_get = array( 'unserialize(seat_types)' ); 546 | } 547 | 548 | Database Connection 549 | ------------------- 550 | 551 | The class will automatically use the default database connection, and even load it for you if you haven't yet. 552 | 553 | You can specify a database connection on a per-model basis by declaring the _$\_db\_group_ instance variable. This is equivalent to calling `$this->db->database($this->_db_group, TRUE)`. 554 | 555 | See ["Connecting to your Database"](http://ellislab.com/codeigniter/user-guide/database/connecting.html) for more information. 556 | 557 | ```php 558 | class Post_model extends MY_Model 559 | { 560 | public $_db_group = 'group_name'; 561 | } 562 | ``` 563 | 564 | Unit Tests 565 | ---------- 566 | 567 | MY_Model contains a robust set of unit tests to ensure that the system works as planned. 568 | 569 | Install the testing framework (PHPUnit) with Composer: 570 | 571 | $ curl -s https://getcomposer.org/installer | php 572 | $ php composer.phar install 573 | 574 | You can then run the tests using the `vendor/bin/phpunit` binary and specify the tests file: 575 | 576 | $ vendor/bin/phpunit 577 | 578 | 579 | Contributing to MY_Model 580 | ------------------------ 581 | 582 | If you find a bug or want to add a feature to MY_Model, great! In order to make it easier and quicker for me to verify and merge changes in, it would be amazing if you could follow these few basic steps: 583 | 584 | 1. Fork the project. 585 | 2. **Branch out into a new branch. `git checkout -b name_of_new_feature_or_bug`** 586 | 3. Make your feature addition or bug fix. 587 | 4. **Add tests for it. This is important so I don’t break it in a future version unintentionally.** 588 | 5. Commit. 589 | 6. Send me a pull request! 590 | 591 | 592 | Other Documentation 593 | ------------------- 594 | 595 | * My book, The CodeIgniter Handbook, talks about the techniques used in MY_Model and lots of other interesting useful stuff. [Get a copy now.](https://efendibooks.com/books/codeigniter-handbook/vol-1) 596 | * Jeff Madsen has written an excellent tutorial about the basics (and triggered me updating the documentation here). [Read it now, you lovely people.](http://www.codebyjeff.com/blog/2012/01/using-jamie-rumbelows-my_model) 597 | * Rob Allport wrote a post about MY_Model and his experiences with it. [Check it out!](http://www.web-design-talk.co.uk/493/codeigniter-base-models-rock/) 598 | * I've written a write up of the new 2.0.0 features [over at my blog, Jamie On Software.](http://jamieonsoftware.com/journal/2012/9/11/my_model-200-at-a-glance.html) 599 | 600 | Changelog 601 | --------- 602 | 603 | **Version 2.0.0** 604 | * Added support for soft deletes 605 | * Removed Composer support. Great system, CI makes it difficult to use for MY_ classes 606 | * Fixed up all problems with callbacks and consolidated into single `trigger` method 607 | * Added support for relationships 608 | * Added built-in timestamp observers 609 | * The DB connection can now be manually set with `$this->_db`, rather than relying on the `$active_group` 610 | * Callbacks can also now take parameters when setting in callback array 611 | * Added support for column serialisation 612 | * Added support for protected attributes 613 | * Added a `truncate()` method 614 | 615 | **Version 1.3.0** 616 | * Added support for array return types using `$return_type` variable and `as_array()` and `as_object()` methods 617 | * Added PHP5.3 support for the test suite 618 | * Removed the deprecated `MY_Model()` constructor 619 | * Fixed an issue with after_create callbacks (thanks [zbrox](https://github.com/zbrox)!) 620 | * Composer package will now autoload the file 621 | * Fixed the callback example by returning the given/modified data (thanks [druu](https://github.com/druu)!) 622 | * Change order of operations in `_fetch_table()` (thanks [JustinBusschau](https://github.com/JustinBusschau)!) 623 | 624 | **Version 1.2.0** 625 | * Bugfix to `update_many()` 626 | * Added getters for table name and skip validation 627 | * Fix to callback functionality (thanks [titosemi](https://github.com/titosemi)!) 628 | * Vastly improved documentation 629 | * Added a `get_next_id()` method (thanks [gbaldera](https://github.com/gbaldera)!) 630 | * Added a set of unit tests 631 | * Added support for [Composer](http://getcomposer.org/) 632 | 633 | **Version 1.0.0 - 1.1.0** 634 | * Initial Releases 635 | -------------------------------------------------------------------------------- /tests/MY_Model_test.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | use Mockery as m; 11 | 12 | require_once 'tests/support/test_helper.php'; 13 | 14 | class MY_Model_tests extends PHPUnit_Framework_TestCase 15 | { 16 | public $model; 17 | 18 | /* -------------------------------------------------------------- 19 | * TEST INFRASTRUCTURE 20 | * ------------------------------------------------------------ */ 21 | 22 | public function setUp() 23 | { 24 | $this->model = new Record_model(); 25 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 26 | } 27 | 28 | public function tearDown() 29 | { 30 | unset($this->model); 31 | } 32 | 33 | /* -------------------------------------------------------------- 34 | * GENERIC METHODS 35 | * ------------------------------------------------------------ */ 36 | 37 | public function test_constructor_guesses_the_table_name() 38 | { 39 | $this->model = new Record_model(); 40 | 41 | $this->assertEquals($this->model->table(), 'records'); 42 | } 43 | 44 | /* -------------------------------------------------------------- 45 | * CRUD INTERFACE 46 | * ------------------------------------------------------------ */ 47 | 48 | public function test_get() 49 | { 50 | $this->model->_database->expects($this->once()) 51 | ->method('where') 52 | ->with($this->equalTo('id'), $this->equalTo(2)) 53 | ->will($this->returnValue($this->model->_database)); 54 | $this->_expect_get(); 55 | $this->model->_database->expects($this->once()) 56 | ->method('row') 57 | ->will($this->returnValue('fake_record_here')); 58 | 59 | $this->assertEquals($this->model->get(2), 'fake_record_here'); 60 | } 61 | 62 | public function test_get_by() 63 | { 64 | $this->model->_database->expects($this->once()) 65 | ->method('where') 66 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 67 | ->will($this->returnValue($this->model->_database)); 68 | $this->_expect_get(); 69 | $this->model->_database->expects($this->once()) 70 | ->method('row') 71 | ->will($this->returnValue('fake_record_here')); 72 | 73 | $this->assertEquals($this->model->get_by('some_column', 'some_value'), 'fake_record_here'); 74 | } 75 | 76 | public function test_get_many() 77 | { 78 | $this->model->_database->expects($this->once()) 79 | ->method('where_in') 80 | ->with($this->equalTo('id'), $this->equalTo(array(1, 2, 3, 4, 5))) 81 | ->will($this->returnValue($this->model->_database)); 82 | $this->_expect_get(); 83 | $this->model->_database->expects($this->once()) 84 | ->method('result') 85 | ->will($this->returnValue(array('fake', 'records', 'here'))); 86 | 87 | $this->assertEquals($this->model->get_many(array(1, 2, 3, 4, 5)), array('fake', 'records', 'here')); 88 | } 89 | 90 | public function test_get_many_by() 91 | { 92 | $this->model->_database->expects($this->once()) 93 | ->method('where') 94 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 95 | ->will($this->returnValue($this->model->_database)); 96 | $this->_expect_get(); 97 | $this->model->_database->expects($this->once()) 98 | ->method('result') 99 | ->will($this->returnValue(array('fake', 'records', 'here'))); 100 | 101 | $this->assertEquals($this->model->get_many_by('some_column', 'some_value'), array('fake', 'records', 'here')); 102 | } 103 | 104 | public function test_get_all() 105 | { 106 | $this->_expect_get(); 107 | $this->model->_database->expects($this->once()) 108 | ->method('result') 109 | ->will($this->returnValue(array('fake', 'records', 'here'))); 110 | 111 | $this->assertEquals($this->model->get_all(), array('fake', 'records', 'here')); 112 | } 113 | 114 | public function test_insert() 115 | { 116 | $this->model->_database->expects($this->once()) 117 | ->method('insert') 118 | ->with($this->equalTo('records'), $this->equalTo(array('new' => 'data'))); 119 | $this->model->_database->expects($this->any()) 120 | ->method('insert_id') 121 | ->will($this->returnValue(123)); 122 | 123 | $this->assertEquals($this->model->insert(array('new' => 'data')), 123); 124 | } 125 | 126 | public function test_insert_many() 127 | { 128 | $this->model->_database->expects($this->exactly(2)) 129 | ->method('insert') 130 | ->with($this->equalTo('records')); 131 | $this->model->_database->expects($this->any()) 132 | ->method('insert_id') 133 | ->will($this->returnValue(123)); 134 | 135 | $this->assertEquals($this->model->insert_many(array(array('new' => 'data'), array('other' => 'data'))), array(123, 123)); 136 | } 137 | 138 | public function test_update() 139 | { 140 | $this->model->_database->expects($this->once()) 141 | ->method('where') 142 | ->with($this->equalTo('id'), $this->equalTo(2)) 143 | ->will($this->returnValue($this->model->_database)); 144 | $this->model->_database->expects($this->once()) 145 | ->method('set') 146 | ->with($this->equalTo(array('new' => 'data'))) 147 | ->will($this->returnValue($this->model->_database)); 148 | $this->model->_database->expects($this->once()) 149 | ->method('update') 150 | ->with($this->equalTo('records')) 151 | ->will($this->returnValue(TRUE)); 152 | 153 | $this->assertEquals($this->model->update(2, array('new' => 'data')), TRUE); 154 | } 155 | 156 | public function test_update_many() 157 | { 158 | $this->model->_database->expects($this->once()) 159 | ->method('where_in') 160 | ->with($this->equalTo('id'), $this->equalTo(array(1, 2, 3, 4, 5))) 161 | ->will($this->returnValue($this->model->_database)); 162 | $this->model->_database->expects($this->once()) 163 | ->method('set') 164 | ->with($this->equalTo(array('new' => 'data'))) 165 | ->will($this->returnValue($this->model->_database)); 166 | $this->model->_database->expects($this->once()) 167 | ->method('update') 168 | ->with($this->equalTo('records')) 169 | ->will($this->returnValue(TRUE)); 170 | 171 | $this->assertEquals($this->model->update_many(array(1, 2, 3, 4, 5), array('new' => 'data')), TRUE); 172 | } 173 | 174 | public function test_update_by() 175 | { 176 | $this->model->_database->expects($this->once()) 177 | ->method('where') 178 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 179 | ->will($this->returnValue($this->model->_database)); 180 | $this->model->_database->expects($this->once()) 181 | ->method('set') 182 | ->with($this->equalTo(array('new' => 'data'))) 183 | ->will($this->returnValue($this->model->_database)); 184 | $this->model->_database->expects($this->once()) 185 | ->method('update') 186 | ->with($this->equalTo('records')) 187 | ->will($this->returnValue(TRUE)); 188 | 189 | $this->assertEquals($this->model->update_by('some_column', 'some_value', array('new' => 'data')), TRUE); 190 | } 191 | 192 | public function test_update_all() 193 | { 194 | $this->model->_database->expects($this->once()) 195 | ->method('set') 196 | ->with($this->equalTo(array('new' => 'data'))) 197 | ->will($this->returnValue($this->model->_database)); 198 | $this->model->_database->expects($this->once()) 199 | ->method('update') 200 | ->with($this->equalTo('records')) 201 | ->will($this->returnValue(TRUE)); 202 | 203 | $this->assertEquals($this->model->update_all(array('new' => 'data')), TRUE); 204 | } 205 | 206 | public function test_delete() 207 | { 208 | $this->model->_database->expects($this->once()) 209 | ->method('where') 210 | ->with($this->equalTo('id'), $this->equalTo(2)) 211 | ->will($this->returnValue($this->model->_database)); 212 | $this->model->_database->expects($this->once()) 213 | ->method('delete') 214 | ->with($this->equalTo('records')) 215 | ->will($this->returnValue(TRUE)); 216 | 217 | $this->assertEquals($this->model->delete(2), TRUE); 218 | } 219 | 220 | public function test_delete_by() 221 | { 222 | $this->model->_database->expects($this->once()) 223 | ->method('where') 224 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 225 | ->will($this->returnValue($this->model->_database)); 226 | $this->model->_database->expects($this->once()) 227 | ->method('delete') 228 | ->with($this->equalTo('records')) 229 | ->will($this->returnValue(TRUE)); 230 | 231 | $this->assertEquals($this->model->delete_by('some_column', 'some_value'), TRUE); 232 | } 233 | 234 | public function test_delete_many() 235 | { 236 | $this->model->_database->expects($this->once()) 237 | ->method('where_in') 238 | ->with($this->equalTo('id'), array(1, 2, 3, 4, 5)) 239 | ->will($this->returnValue($this->model->_database)); 240 | $this->model->_database->expects($this->once()) 241 | ->method('delete') 242 | ->with($this->equalTo('records')) 243 | ->will($this->returnValue(TRUE)); 244 | 245 | $this->assertEquals($this->model->delete_many(array(1, 2, 3, 4, 5)), TRUE); 246 | } 247 | 248 | /* -------------------------------------------------------------- 249 | * MORE CALLBACK TESTS 250 | * ------------------------------------------------------------ */ 251 | 252 | public function test_before_create_callbacks() 253 | { 254 | $this->model = new Before_callback_model(); 255 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 256 | 257 | $row = array( 'one' => 'ONE', 'two' => 'TWO' ); 258 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 259 | 260 | $this->model->_database->expects($this->once()) 261 | ->method('insert') 262 | ->with($this->equalTo('records'), $this->equalTo($expected_row)); 263 | 264 | $this->model->insert($row); 265 | } 266 | 267 | public function test_after_create_callbacks() 268 | { 269 | $this->model = new After_callback_model(); 270 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 271 | 272 | $this->model->_database->expects($this->once()) 273 | ->method('insert_id') 274 | ->will($this->returnValue(10)); 275 | 276 | $self =& $this; 277 | 278 | $this->assertCallbackIsCalled(function() use ($self) 279 | { 280 | $self->model->insert(array( 'row' => 'here' )); 281 | }, 10); 282 | } 283 | 284 | public function test_before_update_callbacks() 285 | { 286 | $this->model = new Before_callback_model(); 287 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 288 | 289 | $row = array( 'one' => 'ONE', 'two' => 'TWO' ); 290 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 291 | 292 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 293 | $this->model->_database->expects($this->once()) 294 | ->method('set') 295 | ->with($this->equalTo($expected_row)) 296 | ->will($this->returnValue($this->model->_database)); 297 | 298 | $this->model->update(1, $row); 299 | } 300 | 301 | public function test_after_update_callbacks() 302 | { 303 | $this->model = new After_callback_model(); 304 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 305 | 306 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 307 | $this->model->_database->expects($this->once())->method('set')->will($this->returnValue($this->model->_database)); 308 | $this->model->_database->expects($this->once())->method('update')->will($this->returnValue(TRUE)); 309 | 310 | $self =& $this; 311 | 312 | $this->assertCallbackIsCalled(function() use ($self) 313 | { 314 | $self->model->update(1, array( 'row' => 'here' )); 315 | }, array( array( 'row' => 'here' ), true )); 316 | } 317 | 318 | public function test_before_get_callbacks() 319 | { 320 | $this->model = new Before_callback_model(); 321 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 322 | 323 | $self =& $this; 324 | 325 | $this->assertCallbackIsCalled(function() use ($self) 326 | { 327 | $self->model->get(1); 328 | }, NULL); 329 | } 330 | 331 | public function test_after_get_callbacks() 332 | { 333 | $this->model = new After_callback_model(); 334 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 335 | 336 | $db_row = array( 'one' => 'ONE', 'two' => 'TWO' ); 337 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 338 | 339 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 340 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 341 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($db_row)); 342 | 343 | $this->assertEquals($expected_row, $this->model->get(1)); 344 | } 345 | 346 | public function test_before_delete_callbacks() 347 | { 348 | $this->model = new Before_callback_model(); 349 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 350 | 351 | $self =& $this; 352 | 353 | $this->assertCallbackIsCalled(function() use ($self) 354 | { 355 | $self->model->delete(12); 356 | }, 12); 357 | } 358 | 359 | public function test_after_delete_callbacks() 360 | { 361 | $this->model = new After_callback_model(); 362 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 363 | 364 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 365 | $this->model->_database->expects($this->once())->method('delete')->will($this->returnValue(TRUE)); 366 | 367 | $self =& $this; 368 | 369 | $this->assertCallbackIsCalled(function() use ($self) 370 | { 371 | $self->model->delete(9); 372 | }, TRUE); 373 | } 374 | 375 | public function test_callbacks_support_parameters() 376 | { 377 | $this->model = new Callback_parameter_model(); 378 | 379 | $self =& $this; 380 | $callback_parameters = array( 381 | 'some_param', 'another_param' 382 | ); 383 | 384 | $this->assertCallbackIsCalled(function() use ($self) 385 | { 386 | $self->model->some_method(); 387 | }, $callback_parameters); 388 | } 389 | 390 | /** 391 | * Callbacks, if called in an array, should receive a "last" boolean 392 | * when they're in the last iteration of triggering - the last row in a result 393 | * array, for instance - for clearing things up 394 | */ 395 | public function test_callbacks_in_iteration_have_last_variable() 396 | { 397 | // stub 398 | } 399 | 400 | /* -------------------------------------------------------------- 401 | * PROTECTED ATTRIBUTES 402 | * ------------------------------------------------------------ */ 403 | 404 | public function test_protected_attributes() 405 | { 406 | $this->model = new Protected_attributes_model(); 407 | 408 | $author = array( 409 | 'id' => 123, 410 | 'hash' => 'dlkadflsdasdsadsds', 411 | 'title' => 'A new post' 412 | ); 413 | $author_obj = (object)$author; 414 | 415 | $author = $this->model->protect_attributes($author); 416 | $author_obj = $this->model->protect_attributes($author_obj); 417 | 418 | $this->assertFalse(isset($author['id'])); 419 | $this->assertFalse(isset($author['hash'])); 420 | $this->assertFalse(isset($author_obj->id)); 421 | $this->assertFalse(isset($author_obj->hash)); 422 | } 423 | 424 | /* -------------------------------------------------------------- 425 | * RELATIONSHIPS 426 | * ------------------------------------------------------------ */ 427 | 428 | public function test_belongs_to() 429 | { 430 | $object = (object)array( 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43 ); 431 | $author_object = (object)array( 'id' => 43, 'name' => 'Jamie', 'age' => 20 ); 432 | $expected_object = (object)array( 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43, 'author' => $author_object ); 433 | 434 | $self =& $this; 435 | 436 | $this->model = new Belongs_to_model(); 437 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 438 | $this->model->load = $this->getMock('MY_Model_Mock_Loader'); 439 | 440 | $author_model = new Author_model(); 441 | $author_model->_database = $this->getMockBuilder('MY_Model_Mock_DB')->setMockClassName('Other_Mock_MY_Model_Mock_DB')->getMock(); 442 | 443 | $author_model->_database->expects($this->once())->method('where')->will($this->returnValue($author_model->_database)); 444 | $author_model->_database->expects($this->once())->method('get')->will($this->returnValue($author_model->_database)); 445 | 446 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 447 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 448 | 449 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($object)); 450 | $author_model->_database->expects($this->once())->method('row')->will($this->returnValue($author_object)); 451 | 452 | $this->model->load->expects($this->once())->method('model')->with('author_model', 'author_model') 453 | ->will($this->returnCallback(function() use ($self, $author_model){ 454 | $self->model->author_model = $author_model; 455 | })); 456 | 457 | $this->assertEquals($expected_object, $this->model->with('author')->get(1)); 458 | } 459 | 460 | public function test_has_many() 461 | { 462 | $object = (object)array( 'id' => 1, 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43 ); 463 | 464 | $comment_object = (object)array( 'id' => 1, 'comment' => 'A comment' ); 465 | $comment_object_2 = (object)array( 'id' => 2, 'comment' => 'Another comment' ); 466 | 467 | $expected_object = (object)array( 'id' => 1, 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43, 468 | 'comments' => array( $comment_object, $comment_object_2 ) ); 469 | 470 | $self =& $this; 471 | 472 | $this->model = new Belongs_to_model(); 473 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 474 | $this->model->load = $this->getMock('MY_Model_Mock_Loader'); 475 | 476 | $comment_model = new Author_model(); 477 | $comment_model->_database = $this->getMockBuilder('MY_Model_Mock_DB')->setMockClassName('Another_Mock_MY_Model_Mock_DB')->getMock(); 478 | 479 | $comment_model->_database->expects($this->once())->method('where')->with('comment_id', 1)->will($this->returnValue($comment_model->_database)); 480 | $comment_model->_database->expects($this->once())->method('get')->will($this->returnValue($comment_model->_database)); 481 | 482 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 483 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 484 | 485 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($object)); 486 | $comment_model->_database->expects($this->once())->method('result')->will($this->returnValue(array( $comment_object, $comment_object_2 ))); 487 | 488 | $this->model->load->expects($this->once())->method('model')->with('comment_model', 'comments_model') 489 | ->will($this->returnCallback(function() use ($self, $comment_model){ 490 | $self->model->comments_model = $comment_model; 491 | })); 492 | 493 | $this->assertEquals($expected_object, $this->model->with('comments')->get(1)); 494 | } 495 | 496 | public function test_relate_works_with_objects_and_arrays() 497 | { 498 | $data = array( 'name' => 'Jamie', 'author_id' => 1 ); 499 | $author = 'related object'; 500 | 501 | $this->model = new Belongs_to_model(); 502 | $this->model->author_model = m::mock(new Author_model()); 503 | $this->model->author_model->shouldReceive('get') 504 | ->andReturn($author); 505 | 506 | $obj = $this->model->with('author')->relate((object)$data); 507 | $arr = $this->model->with('author')->relate($data); 508 | 509 | $this->assertInternalType('object', $obj); 510 | $this->assertInternalType('array', $arr); 511 | $this->assertTrue(isset($obj->author)); 512 | $this->assertTrue(isset($arr['author'])); 513 | $this->assertEquals($author, $obj->author); 514 | $this->assertEquals($author, $arr['author']); 515 | } 516 | 517 | /* -------------------------------------------------------------- 518 | * VALIDATION 519 | * ------------------------------------------------------------ */ 520 | 521 | public function test_validate_correctly_returns_the_data_on_success_and_FALSE_on_failure() 522 | { 523 | $this->model = $this->_validatable_model(); 524 | $data = array( 'name' => 'Jamie', 'sexyness' => 'loads' ); 525 | 526 | $this->assertEquals($this->model->validate($data), $data); 527 | 528 | $this->model = $this->_validatable_model(FALSE); 529 | $this->assertEquals($this->model->validate($data), FALSE); 530 | } 531 | 532 | public function test_skip_validation() 533 | { 534 | $ret = $this->model->skip_validation(); 535 | 536 | $this->assertEquals($ret, $this->model); 537 | $this->assertEquals($this->model->get_skip_validation(), TRUE); 538 | } 539 | 540 | protected function _validatable_model($validate_pass_or_fail = TRUE) 541 | { 542 | $model = new Validated_model(); 543 | $model->form_validation = m::mock('form_validation_class'); 544 | $model->form_validation->shouldIgnoreMissing(); 545 | $model->form_validation->shouldReceive('run') 546 | ->andReturn($validate_pass_or_fail); 547 | 548 | return $model; 549 | } 550 | 551 | /* -------------------------------------------------------------- 552 | * SOFT DELETE 553 | * ------------------------------------------------------------ */ 554 | 555 | public function test_soft_delete() 556 | { 557 | $this->model = new Soft_delete_model(); 558 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 559 | 560 | $this->model->_database->expects($this->once()) 561 | ->method('where') 562 | ->with($this->equalTo('id'), $this->equalTo(2)) 563 | ->will($this->returnValue($this->model->_database)); 564 | $this->model->_database->expects($this->once()) 565 | ->method('update') 566 | ->with($this->equalTo('records'), $this->equalTo(array( 'deleted' => TRUE ))) 567 | ->will($this->returnValue(TRUE)); 568 | 569 | $this->assertEquals($this->model->delete(2), TRUE); 570 | } 571 | 572 | public function test_soft_delete_custom_key() 573 | { 574 | $this->model = new Soft_delete_model('record_deleted'); 575 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 576 | 577 | $this->model->_database->expects($this->once()) 578 | ->method('where') 579 | ->with($this->equalTo('id'), $this->equalTo(2)) 580 | ->will($this->returnValue($this->model->_database)); 581 | $this->model->_database->expects($this->once()) 582 | ->method('update') 583 | ->with($this->equalTo('records'), $this->equalTo(array( 'record_deleted' => TRUE ))) 584 | ->will($this->returnValue(TRUE)); 585 | 586 | $this->assertEquals($this->model->delete(2), TRUE); 587 | } 588 | 589 | public function test_soft_delete_by() 590 | { 591 | $this->model = new Soft_delete_model(); 592 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 593 | 594 | $this->model->_database->expects($this->once()) 595 | ->method('where') 596 | ->with($this->equalTo('key'), $this->equalTo('value')) 597 | ->will($this->returnValue($this->model->_database)); 598 | $this->model->_database->expects($this->once()) 599 | ->method('update') 600 | ->with($this->equalTo('records'), $this->equalTo(array( 'deleted' => TRUE ))) 601 | ->will($this->returnValue(TRUE)); 602 | 603 | $this->assertEquals($this->model->delete_by('key', 'value'), TRUE); 604 | } 605 | 606 | public function test_soft_delete_many() 607 | { 608 | $this->model = new Soft_delete_model(); 609 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 610 | 611 | $this->model->_database->expects($this->once()) 612 | ->method('where_in') 613 | ->with($this->equalTo('id'), $this->equalTo(array(2, 4, 6))) 614 | ->will($this->returnValue($this->model->_database)); 615 | $this->model->_database->expects($this->once()) 616 | ->method('update') 617 | ->with($this->equalTo('records'), $this->equalTo(array( 'deleted' => TRUE ))) 618 | ->will($this->returnValue(TRUE)); 619 | 620 | $this->assertEquals($this->model->delete_many(array(2, 4, 6)), TRUE); 621 | } 622 | 623 | public function test_soft_delete_get() 624 | { 625 | $this->model = new Soft_delete_model(); 626 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 627 | 628 | $this->model->_database->expects($this->at(0)) 629 | ->method('where') 630 | ->with($this->equalTo('deleted'), $this->equalTo(FALSE)) 631 | ->will($this->returnValue($this->model->_database)); 632 | $this->model->_database->expects($this->at(1)) 633 | ->method('where') 634 | ->with($this->equalTo('id'), $this->equalTo(2)) 635 | ->will($this->returnValue($this->model->_database)); 636 | $this->_expect_get(); 637 | $this->model->_database->expects($this->once()) 638 | ->method('row') 639 | ->will($this->returnValue('fake_record_here')); 640 | 641 | $this->assertEquals($this->model->get(2), 'fake_record_here'); 642 | } 643 | 644 | public function test_soft_delete_dropdown() 645 | { 646 | $this->model = new Soft_delete_model(); 647 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 648 | 649 | $fake_row_1 = array( 'id' => 1, 'name' => 'Jamie' ); 650 | $fake_row_2 = array( 'id' => 2, 'name' => 'Laura' ); 651 | $fake_results = array( (object)$fake_row_1, (object)$fake_row_2 ); 652 | 653 | $this->model->_database->expects($this->at(0)) 654 | ->method('where') 655 | ->with($this->equalTo('deleted'), $this->equalTo(FALSE)) 656 | ->will($this->returnValue($this->model->_database)); 657 | 658 | $this->model->_database->expects($this->once()) 659 | ->method('select') 660 | ->with($this->equalTo(array('id', 'name'))) 661 | ->will($this->returnValue($this->model->_database)); 662 | $this->_expect_get(); 663 | $this->model->_database->expects($this->any()) 664 | ->method('result') 665 | ->will($this->returnValue($fake_results)); 666 | 667 | $this->model->dropdown('name'); 668 | } 669 | 670 | public function test_with_deleted() 671 | { 672 | $this->model = new Soft_delete_model(); 673 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 674 | 675 | $this->model->_database->expects($this->exactly(1)) 676 | ->method('where') 677 | ->with($this->equalTo('id'), $this->equalTo(2)) 678 | ->will($this->returnValue($this->model->_database)); 679 | $this->_expect_get(); 680 | $this->model->_database->expects($this->once()) 681 | ->method('row') 682 | ->will($this->returnValue('fake_record_here')); 683 | 684 | $this->assertEquals($this->model->with_deleted()->get(2), 'fake_record_here'); 685 | } 686 | 687 | public function test_only_deleted() 688 | { 689 | $this->model = new Soft_delete_model(); 690 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 691 | 692 | $this->model->_database->expects($this->once()) 693 | ->method('where') 694 | ->with($this->equalTo('deleted'), $this->equalTo(TRUE)) 695 | ->will($this->returnValue($this->model->_database)); 696 | $this->_expect_get(); 697 | $this->model->_database->expects($this->once()) 698 | ->method('result') 699 | ->will($this->returnValue(array('fake_record_here'))); 700 | 701 | $this->assertEquals($this->model->only_deleted()->get_all(), array('fake_record_here')); 702 | } 703 | 704 | /* -------------------------------------------------------------- 705 | * CALLBACKS 706 | * ------------------------------------------------------------ */ 707 | 708 | public function test_serialize() 709 | { 710 | $this->model = new Serialised_data_model(); 711 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 712 | 713 | $data = array( 'name' => 'Jamie', 'awesomeness_level' => 1000000 ); 714 | 715 | $this->model->_database->expects($this->exactly(1)) 716 | ->method('insert') 717 | ->with($this->equalTo('records'), $this->equalTo(array( 'data' => serialize($data) ))); 718 | 719 | $this->model->insert(array( 'data' => $data )); 720 | } 721 | 722 | public function test_timestamps() 723 | { 724 | $this->model = new Record_model(); 725 | 726 | $data = array( 'name' => 'Jamie' ); 727 | $obj = (object)array( 'name' => 'Jamie' ); 728 | 729 | $data = $this->model->created_at($data); 730 | $obj = $this->model->created_at($obj); 731 | $data = $this->model->updated_at($data); 732 | $obj = $this->model->updated_at($obj); 733 | 734 | $this->assertTrue(isset($data['created_at'])); 735 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $data['created_at']); 736 | $this->assertTrue(isset($obj->created_at)); 737 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->created_at); 738 | $this->assertTrue(isset($data['updated_at'])); 739 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $data['updated_at']); 740 | $this->assertTrue(isset($obj->updated_at)); 741 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->updated_at); 742 | } 743 | 744 | /* -------------------------------------------------------------- 745 | * UTILITY METHODS 746 | * ------------------------------------------------------------ */ 747 | 748 | public function test_dropdown() 749 | { 750 | $fake_row_1 = array( 'id' => 1, 'name' => 'Jamie' ); 751 | $fake_row_2 = array( 'id' => 2, 'name' => 'Laura' ); 752 | 753 | $fake_results = array( (object)$fake_row_1, (object)$fake_row_2 ); 754 | 755 | $this->model->_database->expects($this->once()) 756 | ->method('select') 757 | ->with($this->equalTo(array('id', 'name'))) 758 | ->will($this->returnValue($this->model->_database)); 759 | $this->_expect_get(); 760 | $this->model->_database->expects($this->any()) 761 | ->method('result') 762 | ->will($this->returnValue($fake_results)); 763 | 764 | $this->assertEquals($this->model->dropdown('name'), array( 1 => 'Jamie', 2 => 'Laura' )); 765 | } 766 | 767 | public function test_count_by() 768 | { 769 | $this->model->_database->expects($this->once()) 770 | ->method('where') 771 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 772 | ->will($this->returnValue($this->model->_database)); 773 | $this->model->_database->expects($this->once()) 774 | ->method('count_all_results') 775 | ->will($this->returnValue(5)); 776 | 777 | $this->assertEquals($this->model->count_by('some_column', 'some_value'), 5); 778 | } 779 | 780 | public function test_count_all() 781 | { 782 | $this->model->_database->expects($this->once()) 783 | ->method('count_all') 784 | ->with($this->equalTo('records')) 785 | ->will($this->returnValue(200)); 786 | $this->assertEquals($this->model->count_all(), 200); 787 | } 788 | 789 | public function test_get_next_id() 790 | { 791 | $this->model->_database->database = 'some_database_name'; 792 | 793 | $this->model->_database->expects($this->once()) 794 | ->method('select') 795 | ->with($this->equalTo('AUTO_INCREMENT')) 796 | ->will($this->returnValue($this->model->_database)); 797 | $this->model->_database->expects($this->once()) 798 | ->method('from') 799 | ->with($this->equalTo('information_schema.TABLES')) 800 | ->will($this->returnValue($this->model->_database)); 801 | $this->model->_database->expects($this->any()) 802 | ->method('where') 803 | ->will($this->returnValue($this->model->_database)); 804 | $this->model->_database->expects($this->once()) 805 | ->method('get') 806 | ->will($this->returnValue($this->model->_database)); 807 | $this->model->_database->expects($this->once()) 808 | ->method('row') 809 | ->will($this->returnValue((object)array( 'AUTO_INCREMENT' => 250 ))); 810 | 811 | $this->assertEquals($this->model->get_next_id(), 250); 812 | } 813 | 814 | public function test_as_array() 815 | { 816 | $this->model->_database->expects($this->once()) 817 | ->method('where') 818 | ->with($this->equalTo('id'), $this->equalTo(2)) 819 | ->will($this->returnValue($this->model->_database)); 820 | $this->_expect_get(); 821 | $this->model->_database->expects($this->once()) 822 | ->method('row_array') 823 | ->will($this->returnValue('fake_record_here')); 824 | 825 | $this->assertEquals($this->model->as_array()->get(2), 'fake_record_here'); 826 | } 827 | 828 | /* -------------------------------------------------------------- 829 | * QUERY BUILDER DIRECT ACCESS METHODS 830 | * ------------------------------------------------------------ */ 831 | 832 | public function test_order_by_regular() 833 | { 834 | $this->model->_database->expects($this->once()) 835 | ->method('order_by') 836 | ->with($this->equalTo('some_column'), $this->equalTo('DESC')); 837 | 838 | $this->assertEquals($this->model->order_by('some_column', 'DESC'), $this->model); 839 | } 840 | 841 | public function test_order_by_array() 842 | { 843 | $this->model->_database->expects($this->once()) 844 | ->method('order_by') 845 | ->with($this->equalTo('some_column'), $this->equalTo('ASC')); 846 | 847 | $this->assertEquals($this->model->order_by(array('some_column' => 'ASC')), $this->model); 848 | } 849 | 850 | public function test_limit() 851 | { 852 | $this->model->_database->expects($this->once()) 853 | ->method('limit') 854 | ->with($this->equalTo(10), $this->equalTo(5)); 855 | 856 | $this->assertEquals($this->model->limit(10, 5), $this->model); 857 | } 858 | 859 | public function test_truncate() 860 | { 861 | $this->model->_database->expects($this->once()) 862 | ->method('truncate') 863 | ->with($this->equalTo('records')); 864 | 865 | $this->model->truncate(); 866 | } 867 | 868 | /* -------------------------------------------------------------- 869 | * TEST UTILITIES 870 | * ------------------------------------------------------------ */ 871 | 872 | protected function _expect_get() 873 | { 874 | $this->model->_database->expects($this->once()) 875 | ->method('get') 876 | ->with($this->equalTo('records')) 877 | ->will($this->returnValue($this->model->_database)); 878 | } 879 | 880 | /* -------------------------------------------------------------- 881 | * CUSTOM ASSERTIONS 882 | * ------------------------------------------------------------ */ 883 | 884 | public function assertCallbackIsCalled($method, $params = null) 885 | { 886 | try 887 | { 888 | $method(); 889 | $this->fail('Callback wasn\'t called'); 890 | } 891 | catch (Callback_Test_Exception $e) 892 | { 893 | if (!is_null($params)) 894 | { 895 | $this->assertEquals($e->passed_object, $params); 896 | } 897 | } 898 | } 899 | } -------------------------------------------------------------------------------- /core/MY_Model.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Some modifications have been implemented by Ivan Tcholakov, 2012-2016 10 | * @link https://github.com/ivantcholakov/codeigniter-base-model 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | defined('BASEPATH') OR exit('No direct script access allowed'); 32 | 33 | class MY_Model extends CI_Model 34 | { 35 | 36 | /* -------------------------------------------------------------- 37 | * VARIABLES 38 | * ------------------------------------------------------------ */ 39 | 40 | /** 41 | * This model's default database table. Automatically 42 | * guessed by pluralising the model name. 43 | */ 44 | protected $_table; 45 | 46 | /** 47 | * Specify a database group to manually connect this model 48 | * to the specified DB. You can pass either the group name 49 | * as defined in application/config/database.php, or a 50 | * config array of the same format (basically the same thing 51 | * you can pass to $this->load->database()). If left empty, 52 | * the default DB will be used. 53 | */ 54 | protected $_db_group; 55 | 56 | /** 57 | * The database connection object. Will be set to the default 58 | * connection unless $this->_db_group is specified. This allows 59 | * individual models to use different DBs without overwriting 60 | * CI's global $this->db connection. 61 | */ 62 | public $_database; 63 | 64 | /** 65 | * Here a list of table fields e to be stored when it is required. 66 | */ 67 | protected $_fields = NULL; 68 | 69 | /** 70 | * This model's default primary key or unique identifier. 71 | * Used by the get(), update() and delete() functions. 72 | */ 73 | protected $primary_key = 'id'; 74 | 75 | /** 76 | * Support for soft deletes and this model's 'deleted' key 77 | */ 78 | protected $soft_delete = FALSE; 79 | protected $soft_delete_key = 'deleted'; 80 | protected $_temporary_with_deleted = FALSE; 81 | protected $_temporary_only_deleted = FALSE; 82 | protected $soft_delete_key_full = NULL; // The constructor initializes this. 83 | 84 | /** 85 | * The various callbacks available to the model. Each are 86 | * simple lists of method names (methods will be run on $this). 87 | */ 88 | protected $before_create = array(); 89 | protected $after_create = array(); 90 | protected $before_update = array(); 91 | protected $after_update = array(); 92 | protected $before_get = array(); 93 | protected $after_get = array(); 94 | protected $before_delete = array(); 95 | protected $after_delete = array(); 96 | protected $before_dropdown = array(); 97 | protected $after_dropdown = array(); 98 | 99 | protected $callback_parameters = array(); 100 | 101 | /** 102 | * Support for skip_observers() scope. 103 | */ 104 | protected $_temporary_skip_observers = FALSE; 105 | 106 | /** 107 | * Protected, non-modifiable attributes 108 | */ 109 | protected $protected_attributes = array(); 110 | 111 | /** 112 | * If this flag is TRUE before insert and before update, non-existent 113 | * (within the table) fields from input data are removed. 114 | * Currently works with array-type input data only. 115 | */ 116 | protected $check_for_existing_fields = FALSE; 117 | 118 | /** 119 | * Relationship arrays. Use flat strings for defaults or string 120 | * => array to customise the class name and primary key 121 | */ 122 | protected $belongs_to = array(); 123 | protected $has_many = array(); 124 | 125 | protected $_with = array(); 126 | 127 | /** 128 | * An array of validation rules. This needs to be the same format 129 | * as validation rules passed to the Form_validation library. 130 | */ 131 | protected $validate = array(); 132 | 133 | /** 134 | * Optionally skip the validation. Used in conjunction with 135 | * skip_validation() to skip data validation for any future calls. 136 | */ 137 | protected $skip_validation = FALSE; 138 | 139 | /** 140 | * By default we return our results as objects. If we need to override 141 | * this, we can, or, we could use the `as_array()` and `as_object()` scopes. 142 | */ 143 | protected $return_type = 'object'; 144 | protected $_temporary_return_type = NULL; 145 | 146 | /** 147 | * For the cases when we are intersted to retrieve a single value 148 | * we may use `as_value()` scopes for convenience. 149 | */ 150 | protected $qb_as_value = NULL; 151 | 152 | /** 153 | * For the cases when we are intersted to retrieve a single value 154 | * we may use `as_value()` scopes for convenience. 155 | */ 156 | protected $qb_as_sql = NULL; 157 | 158 | /** 159 | * Additional scope that enforces JSON presentation of the returned result. 160 | */ 161 | protected $_as_json = FALSE; 162 | protected $_as_json_options = 0; 163 | 164 | /** 165 | * A flag indicating $this->distinct() usage. 166 | */ 167 | protected $qb_distinct = NULL; 168 | 169 | /** 170 | * A flag indicating that select() method of this object has been called. 171 | */ 172 | protected $_select_called = FALSE; 173 | 174 | /** 175 | * Saved value about LIMIT clause. 176 | */ 177 | protected $_limit = FALSE; 178 | 179 | /** 180 | * Saved value about OFFSET clause. 181 | */ 182 | protected $_offset = FALSE; 183 | 184 | /** 185 | * CodeIgniter version check. 186 | */ 187 | protected $_is_ci_3 = NULL; 188 | 189 | /** 190 | * Compatibility checks. 191 | */ 192 | protected $_function_exists_array_column = NULL; 193 | 194 | /** 195 | * Driver info. 196 | */ 197 | protected $_dbdriver = NULL; 198 | protected $_subdriver = NULL; 199 | 200 | /** 201 | * Driver specific SQL fragments. 202 | */ 203 | protected $_count_string = 'SELECT COUNT(*) AS '; 204 | 205 | /** 206 | * User ID getter for the observers 'created_by', 'updated_by' and 'deleted_by'. 207 | * It should be a callable type (function() or array($object, 'method')) 208 | * without parameters. If it is not set, User ID is assumed to be null value. 209 | */ 210 | protected $user_id_getter = NULL; 211 | 212 | /* -------------------------------------------------------------- 213 | * GENERIC METHODS 214 | * ------------------------------------------------------------ */ 215 | 216 | /** 217 | * Initialise the model, tie into the CodeIgniter superobject and 218 | * try our best to guess the table name. 219 | */ 220 | public function __construct() 221 | { 222 | parent::__construct(); 223 | 224 | $this->_is_ci_3 = (int) CI_VERSION >= 3; 225 | $this->_function_exists_array_column = function_exists('array_column'); 226 | 227 | $this->load->helper('inflector'); 228 | 229 | $this->_set_database(); 230 | $this->_fetch_table(); 231 | 232 | $this->soft_delete_key_full = $this->_table.'.'.$this->soft_delete_key; 233 | 234 | array_unshift($this->before_create, 'protect_attributes'); 235 | array_unshift($this->before_update, 'protect_attributes'); 236 | 237 | if ($this->check_for_existing_fields) 238 | { 239 | array_unshift($this->before_create, 'existing_fields_only'); 240 | array_unshift($this->before_update, 'existing_fields_only'); 241 | } 242 | 243 | $this->_reset_state(); 244 | 245 | $this->_dbdriver = isset($this->_database->dbdriver) 246 | ? $this->_database->dbdriver 247 | : NULL; 248 | 249 | $this->_subdriver = isset($this->_database->subdriver) 250 | ? $this->_database->subdriver 251 | : NULL; 252 | 253 | if ($this->_dbdriver == 'oci8' || $this->_subdriver == 'oci') { 254 | $this->_count_string = 'SELECT COUNT(1) AS '; 255 | } 256 | } 257 | 258 | public function __clone() 259 | { 260 | if (is_object($this->_database)) 261 | { 262 | // Make a clone of the query builder, so the state of the original one to be preserved. 263 | $this->_database = clone $this->_database; 264 | } 265 | } 266 | 267 | /** 268 | * An empty method that keeps chaining, the parameter does the desired operation as a side-effect. 269 | * 270 | * Sample usage (you want to build the query using one PHP sentence): 271 | * 272 | * $for_male = true; // Assign this using the user input. 273 | * 274 | * $found_products = $this->products 275 | * ->select('id, name') 276 | * ->where('in_stock', 1) 277 | * ->that($for_male ? $this->products->where('for_male', 1) : null) 278 | * ->limit(20) 279 | * ->order_by('price', 'asc') 280 | * ->as_array() 281 | * ->find(); 282 | * 283 | * var_dump($found_products); 284 | * 285 | * @param mixed $expression A (conditional) expression that changes context/scope. 286 | * @return object Returns a reference to the created model instance. 287 | */ 288 | public function that($expression = NULL) 289 | { 290 | return $this; 291 | } 292 | 293 | /* -------------------------------------------------------------- 294 | * CRUD INTERFACE 295 | * ------------------------------------------------------------ */ 296 | 297 | /** 298 | * Fetch a single record based on the primary key. Returns an object. 299 | */ 300 | public function get($primary_value) 301 | { 302 | return $this->get_by($this->primary_key, $primary_value); 303 | } 304 | 305 | /** 306 | * Fetch a single record based on an arbitrary WHERE call. Can be 307 | * any valid value to $this->_database->where(). 308 | */ 309 | public function get_by() 310 | { 311 | $where = func_get_args(); 312 | $this->_set_where($where); 313 | $this->_database->limit(1); 314 | 315 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 316 | { 317 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 318 | } 319 | 320 | $this->trigger('before_get'); 321 | 322 | if ($this->qb_as_sql) 323 | { 324 | // Return an SQL statement as a result. 325 | return $this->_return_sql('select'); 326 | } 327 | 328 | $row = $this->_database 329 | ->get($this->_table) 330 | ->{$this->_return_type()}(); 331 | 332 | $row = $this->trigger('after_get', $row); 333 | 334 | if ($this->qb_as_value) 335 | { 336 | // Return a single value as a result. 337 | return $this->_return_value($row); 338 | } 339 | 340 | if ($this->_as_json) 341 | { 342 | return $this->_return_json($row); 343 | } 344 | 345 | $this->_reset_state(); 346 | 347 | return $row; 348 | } 349 | 350 | /** 351 | * An alias of get_by(). 352 | */ 353 | public function first() 354 | { 355 | $args = func_get_args(); 356 | 357 | return call_user_func_array(array($this, 'get_by'), $args); 358 | } 359 | 360 | /** 361 | * Fetch an array of records based on an array of primary values. 362 | */ 363 | public function get_many($values) 364 | { 365 | $this->_database->where_in($this->primary_key, $values); 366 | 367 | return $this->get_all(); 368 | } 369 | 370 | /** 371 | * Fetch an array of records based on an arbitrary WHERE call. 372 | */ 373 | public function get_many_by() 374 | { 375 | $where = func_get_args(); 376 | $this->_set_where($where); 377 | 378 | return $this->get_all(); 379 | } 380 | 381 | /** 382 | * An alias of get_many_by(). 383 | */ 384 | public function find() 385 | { 386 | $args = func_get_args(); 387 | 388 | return call_user_func_array(array($this, 'get_many_by'), $args); 389 | } 390 | 391 | /** 392 | * Fetch all the records in the table. Can be used as a generic call 393 | * to $this->_database->get() with scoped methods. 394 | */ 395 | public function get_all() 396 | { 397 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 398 | { 399 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 400 | } 401 | 402 | $this->trigger('before_get'); 403 | 404 | if ($this->qb_as_sql) 405 | { 406 | // Return an SQL statement as a result. 407 | return $this->_return_sql('select'); 408 | } 409 | 410 | $result = $this->_database 411 | ->get($this->_table) 412 | ->{$this->_return_type(1)}(); 413 | 414 | foreach ($result as $key => &$row) 415 | { 416 | $row = $this->trigger('after_get', $row, ($key == count($result) - 1)); 417 | } 418 | 419 | if ($this->_as_json) 420 | { 421 | return $this->_return_json($result); 422 | } 423 | 424 | $this->_reset_state(); 425 | 426 | return $result; 427 | } 428 | 429 | /** 430 | * Insert a new row into the table. $data should be an associative array 431 | * of data to be inserted. Returns newly created ID. 432 | */ 433 | public function insert($data, $skip_validation = FALSE, $escape = NULL) 434 | { 435 | $escape = $this->_check_default_escape($escape); 436 | 437 | if ($skip_validation === FALSE) 438 | { 439 | $data = $this->validate($data); 440 | } 441 | 442 | if ($data !== FALSE) 443 | { 444 | $data = $this->trigger('before_create', $data); 445 | 446 | $this->_database->set($data, '', $escape); 447 | 448 | if ($this->qb_as_sql) 449 | { 450 | // Return an SQL statement as a result. 451 | return $this->_return_sql('insert'); 452 | } 453 | 454 | $this->_database->insert($this->_table); 455 | 456 | $insert_id = $this->primary_key != '' ? $this->_database->insert_id() : null; 457 | 458 | $this->trigger('after_create', $insert_id); 459 | 460 | $this->_reset_state(); 461 | 462 | return $insert_id; 463 | } 464 | 465 | $this->_reset_state(); 466 | 467 | return FALSE; 468 | } 469 | 470 | /** 471 | * Insert multiple rows into the table. Returns an array of multiple IDs. 472 | */ 473 | public function insert_many($data, $skip_validation = FALSE, $escape = NULL) 474 | { 475 | $return_sql = $this->qb_as_sql; 476 | $skip_observers = $this->_temporary_skip_observers; 477 | 478 | $ids = array(); 479 | 480 | foreach ($data as $key => $row) 481 | { 482 | $this->qb_as_sql = $return_sql; 483 | $this->_temporary_skip_observers = $skip_observers; 484 | 485 | // A correction by Ivan Tcholakov, 14-DEC-2012. 486 | //$ids[] = $this->insert($row, $skip_validation, ($key == count($data) - 1)); 487 | $ids[] = $this->insert($row, $skip_validation, $escape); 488 | // 489 | } 490 | 491 | return $ids; 492 | } 493 | 494 | /** 495 | * Updated a record based on the primary value. 496 | */ 497 | public function update($primary_value, $data, $skip_validation = FALSE, $escape = NULL) 498 | { 499 | $escape = $this->_check_default_escape($escape); 500 | 501 | $data = $this->trigger('before_update', $data); 502 | 503 | if ($skip_validation === FALSE) 504 | { 505 | $data = $this->validate($data); 506 | } 507 | 508 | if ($data !== FALSE) 509 | { 510 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 511 | { 512 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 513 | } 514 | 515 | $this->_database->where($this->primary_key, $primary_value) 516 | ->set($data, '', $escape); 517 | 518 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 519 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 520 | $this->_database->limit(1); 521 | } 522 | 523 | if ($this->qb_as_sql) 524 | { 525 | // Return an SQL statement as a result. 526 | return $this->_return_sql('update'); 527 | } 528 | 529 | $result = $this->_database->update($this->_table); 530 | 531 | $this->trigger('after_update', array($data, $result, $primary_value)); 532 | 533 | $this->_reset_state(); 534 | 535 | return $result; 536 | } 537 | 538 | $this->_reset_state(); 539 | 540 | return FALSE; 541 | } 542 | 543 | /** 544 | * Update many records, based on an array of primary values. 545 | */ 546 | public function update_many($primary_values, $data, $skip_validation = FALSE, $escape = NULL) 547 | { 548 | $escape = $this->_check_default_escape($escape); 549 | 550 | $data = $this->trigger('before_update', $data); 551 | 552 | if ($skip_validation === FALSE) 553 | { 554 | $data = $this->validate($data); 555 | } 556 | 557 | if ($data !== FALSE) 558 | { 559 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 560 | { 561 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 562 | } 563 | 564 | $this->_database->where_in($this->primary_key, $primary_values) 565 | ->set($data, '', $escape); 566 | 567 | if ($this->qb_as_sql) 568 | { 569 | // Return an SQL statement as a result. 570 | return $this->_return_sql('update'); 571 | } 572 | 573 | $result = $this->_database->update($this->_table); 574 | 575 | $this->trigger('after_update', array($data, $result, $primary_values)); 576 | 577 | $this->_reset_state(); 578 | 579 | return $result; 580 | } 581 | 582 | $this->_reset_state(); 583 | 584 | return FALSE; 585 | } 586 | 587 | /** 588 | * Updated a record based on an arbitrary WHERE clause. 589 | */ 590 | public function update_by() 591 | { 592 | $escape = $this->_check_default_escape(NULL); 593 | 594 | $args = func_get_args(); 595 | $data = array_pop($args); 596 | 597 | if (count($args) < 3) 598 | { 599 | $this->_set_where($args); 600 | } 601 | else 602 | { 603 | $where = array_pop($args); 604 | $this->_set_where($where); 605 | $escape = $this->_check_default_escape($args); 606 | } 607 | 608 | $data = $this->trigger('before_update', $data); 609 | 610 | if ($this->validate($data) !== FALSE) 611 | { 612 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 613 | { 614 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 615 | } 616 | 617 | $this->_database->set($data, '', $escape); 618 | 619 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 620 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 621 | $this->_database->limit(1); 622 | } 623 | 624 | if ($this->qb_as_sql) 625 | { 626 | // Return an SQL statement as a result. 627 | return $this->_return_sql('update'); 628 | } 629 | 630 | $result = $this->_database->update($this->_table); 631 | 632 | $this->trigger('after_update', array($data, $result)); 633 | 634 | $this->_reset_state(); 635 | 636 | return $result; 637 | } 638 | 639 | $this->_reset_state(); 640 | 641 | return FALSE; 642 | } 643 | 644 | /** 645 | * Update many records, based on an arbitrary WHERE clause. 646 | */ 647 | public function update_many_by() 648 | { 649 | $escape = $this->_check_default_escape(NULL); 650 | 651 | $args = func_get_args(); 652 | $data = array_pop($args); 653 | 654 | if (count($args) < 3) 655 | { 656 | $this->_set_where($args); 657 | } 658 | else 659 | { 660 | $where = array_pop($args); 661 | $this->_set_where($where); 662 | $escape = $this->_check_default_escape($args); 663 | } 664 | 665 | $data = $this->trigger('before_update', $data); 666 | 667 | if ($this->validate($data) !== FALSE) 668 | { 669 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 670 | { 671 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 672 | } 673 | 674 | $this->_database->set($data, '', $escape); 675 | 676 | if ($this->qb_as_sql) 677 | { 678 | // Return an SQL statement as a result. 679 | return $this->_return_sql('update'); 680 | } 681 | 682 | $result = $this->_database->update($this->_table); 683 | 684 | $this->trigger('after_update', array($data, $result)); 685 | 686 | $this->_reset_state(); 687 | 688 | return $result; 689 | } 690 | 691 | $this->_reset_state(); 692 | 693 | return FALSE; 694 | } 695 | 696 | /** 697 | * Update all records 698 | */ 699 | public function update_all($data, $escape = NULL) 700 | { 701 | $escape = $this->_check_default_escape($escape); 702 | 703 | $data = $this->trigger('before_update', $data); 704 | 705 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 706 | { 707 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 708 | } 709 | 710 | $this->_database->set($data, '', $escape); 711 | 712 | if ($this->qb_as_sql) 713 | { 714 | // Return an SQL statement as a result. 715 | return $this->_return_sql('update'); 716 | } 717 | 718 | $result = $this->_database->update($this->_table); 719 | 720 | $this->trigger('after_update', array($data, $result)); 721 | 722 | $this->_reset_state(); 723 | 724 | return $result; 725 | } 726 | 727 | /** 728 | * Delete a row from the table by the primary value 729 | */ 730 | public function delete($id) 731 | { 732 | $this->trigger('before_delete', $id); 733 | 734 | $this->_database->where($this->primary_key, $id); 735 | 736 | if ($this->soft_delete) 737 | { 738 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 739 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 740 | $this->_database->limit(1); 741 | } 742 | 743 | if ($this->qb_as_sql) 744 | { 745 | // Return an SQL statement as a result. 746 | $this->_database->set($this->soft_delete_key_full, TRUE); 747 | return $this->_return_sql('update'); 748 | } 749 | 750 | $result = $this->_database->update($this->_table, array( $this->soft_delete_key_full => TRUE )); 751 | } 752 | else 753 | { 754 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 755 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 756 | $this->_database->limit(1); 757 | } 758 | 759 | if ($this->qb_as_sql) 760 | { 761 | // Return an SQL statement as a result. 762 | return $this->_return_sql('delete'); 763 | } 764 | 765 | $result = $this->_database->delete($this->_table); 766 | } 767 | 768 | $this->trigger('after_delete', $result); 769 | 770 | $this->_reset_state(); 771 | 772 | return $result; 773 | } 774 | 775 | /** 776 | * Delete a row from the database table by an arbitrary WHERE clause 777 | */ 778 | public function delete_by() 779 | { 780 | $where = func_get_args(); 781 | 782 | $where = $this->trigger('before_delete', $where); 783 | 784 | $this->_set_where($where); 785 | 786 | if ($this->soft_delete) 787 | { 788 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 789 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 790 | $this->_database->limit(1); 791 | } 792 | 793 | if ($this->qb_as_sql) 794 | { 795 | // Return an SQL statement as a result. 796 | $this->_database->set($this->soft_delete_key_full, TRUE); 797 | return $this->_return_sql('update'); 798 | } 799 | 800 | $result = $this->_database 801 | ->update($this->_table, array( $this->soft_delete_key_full => TRUE )); 802 | } 803 | else 804 | { 805 | // See http://www.sqlite.org/compile.html#enable_update_delete_limit 806 | if (strpos((string) $this->_dbdriver, 'sqlite') === false && strpos((string) $this->_subdriver, 'sqlite') === false) { 807 | $this->_database->limit(1); 808 | } 809 | 810 | if ($this->qb_as_sql) 811 | { 812 | // Return an SQL statement as a result. 813 | return $this->_return_sql('delete'); 814 | } 815 | 816 | $result = $this->_database->delete($this->_table); 817 | } 818 | 819 | $this->trigger('after_delete', $result); 820 | 821 | $this->_reset_state(); 822 | 823 | return $result; 824 | } 825 | 826 | /** 827 | * Delete many rows from the database table by multiple primary values 828 | */ 829 | public function delete_many($primary_values) 830 | { 831 | $primary_values = $this->trigger('before_delete', $primary_values); 832 | 833 | $this->_database->where_in($this->primary_key, $primary_values); 834 | 835 | if ($this->soft_delete) 836 | { 837 | if ($this->qb_as_sql) 838 | { 839 | // Return an SQL statement as a result. 840 | $this->_database->set($this->soft_delete_key_full, TRUE); 841 | return $this->_return_sql('update'); 842 | } 843 | 844 | $result = $this->_database->update($this->_table, array( $this->soft_delete_key_full => TRUE )); 845 | } 846 | else 847 | { 848 | if ($this->qb_as_sql) 849 | { 850 | // Return an SQL statement as a result. 851 | return $this->_return_sql('delete'); 852 | } 853 | 854 | $result = $this->_database->delete($this->_table); 855 | } 856 | 857 | $this->trigger('after_delete', $result); 858 | 859 | $this->_reset_state(); 860 | 861 | return $result; 862 | } 863 | 864 | /** 865 | * Delete many rows from the database table by an arbitrary WHERE clause 866 | */ 867 | public function delete_many_by() 868 | { 869 | $where = func_get_args(); 870 | 871 | $where = $this->trigger('before_delete', $where); 872 | 873 | $this->_set_where($where); 874 | 875 | if ($this->soft_delete) 876 | { 877 | if ($this->qb_as_sql) 878 | { 879 | // Return an SQL statement as a result. 880 | $this->_database->set($this->soft_delete_key_full, TRUE); 881 | return $this->_return_sql('update'); 882 | } 883 | 884 | $result = $this->_database->update($this->_table, array( $this->soft_delete_key_full => TRUE )); 885 | } 886 | else 887 | { 888 | if ($this->qb_as_sql) 889 | { 890 | // Return an SQL statement as a result. 891 | return $this->_return_sql('delete'); 892 | } 893 | 894 | $result = $this->_database->delete($this->_table); 895 | } 896 | 897 | $this->trigger('after_delete', $result); 898 | 899 | $this->_reset_state(); 900 | 901 | return $result; 902 | } 903 | 904 | /** 905 | * Truncates the table 906 | */ 907 | public function truncate() 908 | { 909 | if ($this->qb_as_sql) 910 | { 911 | // Return an SQL statement as a result. 912 | return $this->_return_sql('truncate'); 913 | } 914 | 915 | $result = $this->_database->truncate($this->_table); 916 | 917 | $this->_reset_state(); 918 | 919 | return $result; 920 | } 921 | 922 | /* -------------------------------------------------------------- 923 | * RELATIONSHIPS 924 | * ------------------------------------------------------------ */ 925 | 926 | public function with($relationship) 927 | { 928 | $this->_with[] = $relationship; 929 | 930 | if (!in_array('relate', $this->after_get)) 931 | { 932 | $this->after_get[] = 'relate'; 933 | } 934 | 935 | return $this; 936 | } 937 | 938 | // This observer is to be suppressed by skip_observers() scope too. 939 | // This might change if there is a good/valid use-case, but let us not 940 | // complicate code for now. 941 | public function relate($row) 942 | { 943 | if (empty($row)) 944 | { 945 | return $row; 946 | } 947 | 948 | foreach ($this->belongs_to as $key => $value) 949 | { 950 | if (is_string($value)) 951 | { 952 | $relationship = $value; 953 | $options = array( 'primary_key' => $value . '_id', 'model' => $value . '_model' ); 954 | } 955 | else 956 | { 957 | $relationship = $key; 958 | $options = $value; 959 | } 960 | 961 | if (in_array($relationship, $this->_with)) 962 | { 963 | $this->load->model($options['model'], $relationship . '_model'); 964 | if (is_object($row)) 965 | { 966 | $row->{$relationship} = $this->{$relationship . '_model'}->get($row->{$options['primary_key']}); 967 | } 968 | else 969 | { 970 | $row[$relationship] = $this->{$relationship . '_model'}->get($row[$options['primary_key']]); 971 | } 972 | } 973 | } 974 | 975 | foreach ($this->has_many as $key => $value) 976 | { 977 | if (is_string($value)) 978 | { 979 | $relationship = $value; 980 | $options = array( 'primary_key' => singular($this->_table) . '_id', 'model' => singular($value) . '_model' ); 981 | } 982 | else 983 | { 984 | $relationship = $key; 985 | $options = $value; 986 | } 987 | 988 | if (in_array($relationship, $this->_with)) 989 | { 990 | $this->load->model($options['model'], $relationship . '_model'); 991 | if (is_object($row)) 992 | { 993 | $row->{$relationship} = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row->{$this->primary_key}); 994 | } 995 | else 996 | { 997 | $row[$relationship] = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row[$this->primary_key]); 998 | } 999 | } 1000 | } 1001 | 1002 | return $row; 1003 | } 1004 | 1005 | /* -------------------------------------------------------------- 1006 | * UTILITY METHODS 1007 | * ------------------------------------------------------------ */ 1008 | 1009 | /** 1010 | * Returns directly a single specified value from the first selected row. 1011 | * Instead of: 1012 | * $user_id = $this->users->select('id')->where('user_id', $user_id)->or_where('email', $email)->as_value()->first(); 1013 | * you may write the following simpler expression: 1014 | * $user_id = $this->users->where('username', $username)->or_where('email', $email)->value('id'); 1015 | * (username and email are assumed as unique in this example) 1016 | * NULL value is returned if no record has been found. 1017 | */ 1018 | public function value($select = '*', $escape = NULL) 1019 | { 1020 | if ($this->_select_called) { 1021 | 1022 | // If select() was previously called, 1023 | // then ignore the arguments, don't call select() twice. 1024 | return $this->as_value()->first(); 1025 | } 1026 | 1027 | return $this->select($select, $escape)->as_value()->first(); 1028 | } 1029 | 1030 | /** 1031 | * Checks whether a single record based on the primary key exists. 1032 | * @param mixed $primary_value 1033 | * @return boolean 1034 | */ 1035 | public function exists($primary_value) 1036 | { 1037 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 1038 | { 1039 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 1040 | } 1041 | 1042 | $this->_database->select($this->primary_key) 1043 | ->where($this->primary_key, $primary_value) 1044 | ->limit(1); 1045 | 1046 | if ($this->qb_as_sql) 1047 | { 1048 | // Return an SQL statement as a result. 1049 | return $this->_return_sql('select'); 1050 | } 1051 | 1052 | $row = $this->_database->get($this->_table)->row_array(); 1053 | 1054 | $result = isset($row[$this->primary_key]); 1055 | 1056 | if ($this->_as_json) 1057 | { 1058 | return $this->_return_json($result); 1059 | } 1060 | 1061 | $this->_reset_state(); 1062 | 1063 | return $result; 1064 | } 1065 | 1066 | /** 1067 | * Prepares and returns an empty record based on all the existing 1068 | * table fields. The returned result may be an array or an object 1069 | * depending on the model's settings. 1070 | * Triggers 'after_get' observers. 1071 | * If not modified by the obervers, the returned field values are NULL's. 1072 | */ 1073 | public function get_empty() 1074 | { 1075 | $row = array_fill_keys($this->fields(), NULL); 1076 | 1077 | if ($this->_temporary_return_type != 'array') 1078 | { 1079 | $row = (object) $row; 1080 | } 1081 | 1082 | $row = $this->trigger('after_get', $row); 1083 | 1084 | if ($this->_as_json) 1085 | { 1086 | return $this->_return_json($row); 1087 | } 1088 | 1089 | $this->_reset_state(); 1090 | 1091 | return $row; 1092 | } 1093 | 1094 | /** 1095 | * Retrieve and generate a form_dropdown friendly array 1096 | */ 1097 | function dropdown() 1098 | { 1099 | $args = func_get_args(); 1100 | 1101 | if(count($args) == 2) 1102 | { 1103 | list($key, $value) = $args; 1104 | } 1105 | else 1106 | { 1107 | $key = $this->primary_key; 1108 | $value = $args[0]; 1109 | } 1110 | 1111 | $this->trigger('before_dropdown', array( $key, $value )); 1112 | 1113 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 1114 | { 1115 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 1116 | } 1117 | 1118 | $this->_database->select(array($key, $value)); 1119 | 1120 | if ($this->qb_as_sql) 1121 | { 1122 | // Return an SQL statement as a result. 1123 | return $this->_return_sql('select'); 1124 | } 1125 | 1126 | $result = $this->_database->get($this->_table)->result_array(); 1127 | 1128 | if ($this->_function_exists_array_column) 1129 | { 1130 | $options = array_column($result, $value, $key); 1131 | } 1132 | else 1133 | { 1134 | $options = array(); 1135 | 1136 | foreach ($result as $row) 1137 | { 1138 | $options[$row[$key]] = $row[$value]; 1139 | } 1140 | } 1141 | 1142 | $options = $this->trigger('after_dropdown', $options); 1143 | 1144 | if ($this->_as_json) 1145 | { 1146 | return $this->_return_json($options); 1147 | } 1148 | 1149 | $this->_reset_state(); 1150 | 1151 | return $options; 1152 | } 1153 | 1154 | /** 1155 | * Fetch a count of rows based on an arbitrary WHERE call. 1156 | */ 1157 | public function count_by() 1158 | { 1159 | $where = func_get_args(); 1160 | $this->_set_where($where); 1161 | 1162 | // Modified by Ivan Tcholakov, 29-MAR-2013. 1163 | /* 1164 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 1165 | { 1166 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 1167 | } 1168 | 1169 | return $this->_database->count_all_results($this->_table); 1170 | */ 1171 | 1172 | return $this->count_all(); 1173 | } 1174 | 1175 | /** 1176 | * Fetch a total count of rows. 1177 | */ 1178 | public function count_all() 1179 | { 1180 | // Modified by Ivan Tcholakov, 29-MAR-2013. 1181 | /* 1182 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 1183 | { 1184 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 1185 | } 1186 | 1187 | return $this->_database->count_all($this->_table); 1188 | */ 1189 | 1190 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) 1191 | { 1192 | $this->_database->where($this->soft_delete_key_full, (bool)$this->_temporary_only_deleted); 1193 | } 1194 | 1195 | if ($this->qb_as_sql) 1196 | { 1197 | // Return an SQL statement as a result. 1198 | 1199 | $result = ($this->qb_distinct === TRUE) 1200 | ? $this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_database->get_compiled_select($this->_table, true)."\n) CI_count_all_results" 1201 | : $this->_count_string.$this->_database->protect_identifiers('numrows'); 1202 | 1203 | $this->_reset_state(); 1204 | 1205 | return $result; 1206 | } 1207 | 1208 | $result = $this->_database->count_all_results($this->_table); 1209 | 1210 | if ($this->_as_json) 1211 | { 1212 | return $this->_return_json($result); 1213 | } 1214 | 1215 | $this->_reset_state(); 1216 | 1217 | return $result; 1218 | } 1219 | 1220 | /** 1221 | * An alias of count_all(); 1222 | */ 1223 | public function count_all_results() 1224 | { 1225 | return $this->count_all(); 1226 | } 1227 | 1228 | /** 1229 | * Tell the class to skip the insert validation 1230 | */ 1231 | public function skip_validation() 1232 | { 1233 | $this->skip_validation = TRUE; 1234 | return $this; 1235 | } 1236 | 1237 | /** 1238 | * Get the skip validation status 1239 | */ 1240 | public function get_skip_validation() 1241 | { 1242 | return $this->skip_validation; 1243 | } 1244 | 1245 | /** 1246 | * Return the next auto increment of the table. Only tested on MySQL. 1247 | */ 1248 | public function get_next_id() 1249 | { 1250 | return (int) $this->_database->select('AUTO_INCREMENT') 1251 | ->from('information_schema.TABLES') 1252 | ->where('TABLE_NAME', $this->_table) 1253 | ->where('TABLE_SCHEMA', $this->_database->database)->get()->row()->AUTO_INCREMENT; 1254 | } 1255 | 1256 | /** 1257 | * A getter for database object. 1258 | */ 1259 | public function database() 1260 | { 1261 | return $this->_database; 1262 | } 1263 | 1264 | /** 1265 | * A setter for database object. 1266 | * Use case example: A cloned query builder may be set on a clone of this model. 1267 | */ 1268 | public function set_database($db) 1269 | { 1270 | $this->_database = $db; 1271 | 1272 | return $this; 1273 | } 1274 | 1275 | /** 1276 | * Getter for the table name 1277 | */ 1278 | public function table() 1279 | { 1280 | return $this->_table; 1281 | } 1282 | 1283 | /** 1284 | * Getter for the primary key. 1285 | */ 1286 | public function primary_key() 1287 | { 1288 | return $this->primary_key; 1289 | } 1290 | 1291 | /** 1292 | * Returns a list of fields as array of strings of the corresponding table. 1293 | * It queries the database only once and caches the result. 1294 | */ 1295 | public function fields() 1296 | { 1297 | if (!is_array($this->_fields)) 1298 | { 1299 | $this->_fields = $this->_database->list_fields($this->_table); 1300 | 1301 | if (empty($this->_fields)) 1302 | { 1303 | $this->_fields = array(); 1304 | } 1305 | } 1306 | 1307 | return $this->_fields; 1308 | } 1309 | 1310 | /** 1311 | * A wrapper to $this->db->list_fields() 1312 | */ 1313 | public function list_fields() 1314 | { 1315 | return $this->_database->list_fields($this->_table); 1316 | } 1317 | 1318 | /** 1319 | * A wrapper to $this->db->field_exists() 1320 | */ 1321 | public function field_exists($field_name) 1322 | { 1323 | return $this->_database->field_exists($field_name, $this->_table); 1324 | } 1325 | 1326 | /** 1327 | * A wrapper to $this->db->field_data() 1328 | */ 1329 | public function field_data() 1330 | { 1331 | return $this->_database->field_data($this->_table); 1332 | } 1333 | 1334 | /** 1335 | * A getter about LIMIT clause. 1336 | */ 1337 | public function get_limit() 1338 | { 1339 | return $this->_limit; 1340 | } 1341 | 1342 | /** 1343 | * A getter about OFFSET clause. 1344 | */ 1345 | public function get_offset() 1346 | { 1347 | return $this->_offset; 1348 | } 1349 | 1350 | /* -------------------------------------------------------------- 1351 | * GLOBAL SCOPES 1352 | * ------------------------------------------------------------ */ 1353 | 1354 | /** 1355 | * Return the next call as an array rather than an object 1356 | */ 1357 | public function as_array() 1358 | { 1359 | $this->_temporary_return_type = 'array'; 1360 | return $this; 1361 | } 1362 | 1363 | /** 1364 | * Return the next call as an object rather than an array 1365 | */ 1366 | public function as_object() 1367 | { 1368 | $this->_temporary_return_type = 'object'; 1369 | return $this; 1370 | } 1371 | 1372 | /** 1373 | * Don't care about soft deleted rows on the next call 1374 | */ 1375 | public function with_deleted($enabled = TRUE) 1376 | { 1377 | $this->_temporary_with_deleted = (bool) $enabled; 1378 | return $this; 1379 | } 1380 | 1381 | /** 1382 | * Only get deleted rows on the next call 1383 | */ 1384 | public function only_deleted($enabled = TRUE) 1385 | { 1386 | $this->_temporary_only_deleted = (bool) $enabled; 1387 | return $this; 1388 | } 1389 | 1390 | /** 1391 | * Converts the return row into a value (extracts the first column). 1392 | */ 1393 | public function as_value() 1394 | { 1395 | $this->qb_as_value = TRUE; 1396 | return $this; 1397 | } 1398 | 1399 | /** 1400 | * Forces returning compiled SQL statement(s) as a result. 1401 | * This scope is intended for debugging purposes. 1402 | */ 1403 | public function as_sql() 1404 | { 1405 | $this->qb_as_sql = TRUE; 1406 | return $this; 1407 | } 1408 | 1409 | /** 1410 | * Forces returning JSON encoded data as a result. 1411 | * @param int $options Same parameter as in json_encode() function (PHP >= 5.3.0) 1412 | * @link http://php.net/manual/en/function.json-encode.php 1413 | * @link http://php.net/manual/en/json.constants.php 1414 | */ 1415 | public function as_json($options = 0) 1416 | { 1417 | $this->_as_json = TRUE; 1418 | $this->_as_json_options = $options; 1419 | return $this; 1420 | } 1421 | 1422 | /** 1423 | * Disables triggering of all the attached/registered observers. 1424 | */ 1425 | public function skip_observers() 1426 | { 1427 | $this->_temporary_skip_observers = TRUE; 1428 | return $this; 1429 | } 1430 | 1431 | /* -------------------------------------------------------------- 1432 | * OBSERVERS 1433 | * ------------------------------------------------------------ */ 1434 | 1435 | /** 1436 | * For supporting the observers below, the table definition should 1437 | * contatin a part of or all the following definitions (MySQL sysntax), 1438 | * depending on which observers you choose to use: 1439 | * `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 1440 | * `created_by` int(11) unsigned NOT NULL DEFAULT '0', 1441 | * `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 1442 | * `updated_by` int(11) unsigned NOT NULL DEFAULT '0', 1443 | * `deleted` tinyint(1) NOT NULL DEFAULT '0', 1444 | * `deleted_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 1445 | * `deleted_by` int(11) unsigned NOT NULL DEFAULT '0', 1446 | */ 1447 | 1448 | /** 1449 | * A timestamp observer, 'before_create' only. 1450 | */ 1451 | public function created_at($row) 1452 | { 1453 | if (is_object($row)) 1454 | { 1455 | $row->created_at = date('Y-m-d H:i:s'); 1456 | } 1457 | else 1458 | { 1459 | $row['created_at'] = date('Y-m-d H:i:s'); 1460 | } 1461 | 1462 | return $row; 1463 | } 1464 | 1465 | /** 1466 | * A timestamp observer, 'before_create' and 'before_update' only. 1467 | */ 1468 | public function updated_at($row) 1469 | { 1470 | if (is_object($row)) 1471 | { 1472 | $row->updated_at = date('Y-m-d H:i:s'); 1473 | } 1474 | else 1475 | { 1476 | $row['updated_at'] = date('Y-m-d H:i:s'); 1477 | } 1478 | 1479 | return $row; 1480 | } 1481 | 1482 | /** 1483 | * A timestamp observer, 'before_delete' only. 1484 | */ 1485 | public function deleted_at($parameter) 1486 | { 1487 | if ($this->soft_delete) 1488 | { 1489 | $this->_database->set($this->_table.'.'.'deleted_at', date('Y-m-d H:i:s')); 1490 | } 1491 | } 1492 | 1493 | /** 1494 | * A user identification observer, 'before_create' only. 1495 | */ 1496 | public function created_by($row) 1497 | { 1498 | if (is_object($row)) 1499 | { 1500 | $row->created_by = $this->_get_user_id(); 1501 | } 1502 | else 1503 | { 1504 | $row['created_by'] = $this->_get_user_id(); 1505 | } 1506 | 1507 | return $row; 1508 | } 1509 | 1510 | /** 1511 | * A user identification observer, 'before_create' and 'before_update' only. 1512 | */ 1513 | public function updated_by($row) 1514 | { 1515 | if (is_object($row)) 1516 | { 1517 | $row->updated_by = $this->_get_user_id(); 1518 | } 1519 | else 1520 | { 1521 | $row['updated_by'] = $this->_get_user_id(); 1522 | } 1523 | 1524 | return $row; 1525 | } 1526 | 1527 | /** 1528 | * A user identification observer, 'before_delete' only. 1529 | */ 1530 | public function deleted_by($parameter) 1531 | { 1532 | if ($this->soft_delete) 1533 | { 1534 | $this->_database->set($this->_table.'.'.'deleted_by', $this->_get_user_id()); 1535 | } 1536 | } 1537 | 1538 | /** 1539 | * Serialises data for you automatically, allowing you to pass 1540 | * through objects and let it handle the serialisation in the background 1541 | */ 1542 | public function serialize($row) 1543 | { 1544 | foreach ($this->callback_parameters as $column) 1545 | { 1546 | $row[$column] = serialize($row[$column]); 1547 | } 1548 | 1549 | return $row; 1550 | } 1551 | 1552 | public function unserialize($row) 1553 | { 1554 | foreach ($this->callback_parameters as $column) 1555 | { 1556 | if (is_array($row)) 1557 | { 1558 | $row[$column] = unserialize($row[$column]); 1559 | } 1560 | else 1561 | { 1562 | $row->$column = unserialize($row->$column); 1563 | } 1564 | } 1565 | 1566 | return $row; 1567 | } 1568 | 1569 | /** 1570 | * Protect attributes by removing them from $row array 1571 | */ 1572 | public function protect_attributes($row) 1573 | { 1574 | foreach ($this->protected_attributes as $attr) 1575 | { 1576 | if (is_object($row)) 1577 | { 1578 | unset($row->$attr); 1579 | } 1580 | else 1581 | { 1582 | unset($row[$attr]); 1583 | } 1584 | } 1585 | 1586 | return $row; 1587 | } 1588 | 1589 | /* 1590 | * Removes non-existent (within the table) fields from input data. 1591 | * Currently works with array-type input data only. 1592 | */ 1593 | public function existing_fields_only($row) 1594 | { 1595 | if (is_array($row)) 1596 | { 1597 | // See array_only() function in Laravel. 1598 | $row = array_intersect_key($row, array_flip((array) $this->fields())); 1599 | } 1600 | 1601 | return $row; 1602 | } 1603 | 1604 | /* -------------------------------------------------------------- 1605 | * QUERY BUILDER DIRECT ACCESS METHODS 1606 | * ------------------------------------------------------------ */ 1607 | 1608 | /** 1609 | * A wrapper to $this->_database->order_by() 1610 | */ 1611 | public function order_by($criteria, $order = '', $escape = NULL) 1612 | { 1613 | if ($this->_is_ci_3) 1614 | { 1615 | if ( is_array($criteria) ) 1616 | { 1617 | foreach ($criteria as $key => $value) 1618 | { 1619 | $this->_database->order_by($key, $value, $escape); 1620 | } 1621 | } 1622 | else 1623 | { 1624 | $this->_database->order_by($criteria, $order, $escape); 1625 | } 1626 | } 1627 | else 1628 | { 1629 | if ( is_array($criteria) ) 1630 | { 1631 | foreach ($criteria as $key => $value) 1632 | { 1633 | $this->_database->order_by($key, $value); 1634 | } 1635 | } 1636 | else 1637 | { 1638 | $this->_database->order_by($criteria, $order); 1639 | } 1640 | } 1641 | 1642 | return $this; 1643 | } 1644 | 1645 | /** 1646 | * A wrapper to $this->_database->limit() 1647 | */ 1648 | public function limit($limit, $offset = 0) 1649 | { 1650 | if ($limit < 0) 1651 | { 1652 | $limit = FALSE; 1653 | } 1654 | 1655 | $this->_limit = $limit; 1656 | 1657 | if ($limit === FALSE) 1658 | { 1659 | $limit = PHP_INT_MAX; 1660 | } 1661 | 1662 | if (!empty($offset)) 1663 | { 1664 | $this->_offset = (int) $offset; 1665 | } 1666 | 1667 | if (!$this->_is_ci_3) 1668 | { 1669 | // Adjust the default value of $offset to match the definition in CI 2.x. 1670 | $offset = empty($offset) ? '' : $offset; 1671 | } 1672 | 1673 | $this->_database->limit($limit, $offset); 1674 | return $this; 1675 | } 1676 | 1677 | /** 1678 | * A wrapper to $this->_database->offset() 1679 | */ 1680 | public function offset($offset) 1681 | { 1682 | $this->_offset = $offset; 1683 | 1684 | if (empty($offset)) 1685 | { 1686 | $this->_offset = FALSE; 1687 | $offset = '00'; 1688 | } 1689 | 1690 | $this->_database->offset($offset); 1691 | return $this; 1692 | } 1693 | 1694 | /** 1695 | * A wrapper to $this->_database->select() 1696 | */ 1697 | public function select($select = '*', $escape = NULL) 1698 | { 1699 | $this->_database->select($select, $escape); 1700 | $this->_select_called = TRUE; 1701 | 1702 | return $this; 1703 | } 1704 | 1705 | /** 1706 | * A wrapper to $this->_database->distinct() 1707 | */ 1708 | public function distinct($val = TRUE) 1709 | { 1710 | $this->qb_distinct = is_bool($val) ? $val : TRUE; 1711 | $this->_database->distinct($val); 1712 | return $this; 1713 | } 1714 | 1715 | /** 1716 | * A wrapper to $this->_database->join() 1717 | */ 1718 | public function join($table, $cond, $type = '', $escape = NULL) 1719 | { 1720 | if ($this->_is_ci_3) 1721 | { 1722 | $this->_database->join($table, $cond, $type, $escape); 1723 | } 1724 | else 1725 | { 1726 | $this->_database->join($table, $cond, $type); 1727 | } 1728 | 1729 | return $this; 1730 | } 1731 | 1732 | /** 1733 | * A wrapper to $this->_database->escape() 1734 | */ 1735 | public function escape($str) 1736 | { 1737 | return $this->_database->escape($str); 1738 | } 1739 | 1740 | /** 1741 | * A wrapper to $this->_database->escape_like_str() 1742 | */ 1743 | public function escape_like_str($str) 1744 | { 1745 | return $this->_database->escape_like_str($str); 1746 | } 1747 | 1748 | /** 1749 | * A wrapper to $this->_database->escape_str() 1750 | */ 1751 | public function escape_str($str, $like = FALSE) 1752 | { 1753 | return $this->_database->escape_str($str, $like); 1754 | } 1755 | 1756 | /** 1757 | * A wrapper to $this->_database->where() 1758 | */ 1759 | public function where($key, $value = NULL, $escape = NULL) 1760 | { 1761 | $escape = $this->_check_default_escape($escape); 1762 | $this->_database->where($key, $value, $escape); 1763 | return $this; 1764 | } 1765 | 1766 | /** 1767 | * A wrapper to $this->_database->or_where() 1768 | */ 1769 | public function or_where($key, $value = NULL, $escape = NULL) 1770 | { 1771 | $escape = $this->_check_default_escape($escape); 1772 | $this->_database->or_where($key, $value, $escape); 1773 | return $this; 1774 | } 1775 | 1776 | /** 1777 | * A wrapper to $this->_database->where_in() 1778 | */ 1779 | public function where_in($key = NULL, $values = NULL, $escape = NULL) 1780 | { 1781 | if ($this->_is_ci_3) 1782 | { 1783 | $this->_database->where_in($key, $values, $escape); 1784 | } 1785 | else 1786 | { 1787 | $this->_database->where_in($key, $values); 1788 | } 1789 | 1790 | return $this; 1791 | } 1792 | 1793 | /** 1794 | * A wrapper to $this->_database->or_where_in() 1795 | */ 1796 | public function or_where_in($key = NULL, $values = NULL, $escape = NULL) 1797 | { 1798 | if ($this->_is_ci_3) 1799 | { 1800 | $this->_database->or_where_in($key, $values, $escape); 1801 | } 1802 | else 1803 | { 1804 | $this->_database->or_where_in($key, $values); 1805 | } 1806 | 1807 | return $this; 1808 | } 1809 | 1810 | /** 1811 | * A wrapper to $this->_database->where_not_in() 1812 | */ 1813 | public function where_not_in($key = NULL, $values = NULL, $escape = NULL) 1814 | { 1815 | if ($this->_is_ci_3) 1816 | { 1817 | $this->_database->where_not_in($key, $values, $escape); 1818 | } 1819 | else 1820 | { 1821 | $this->_database->where_not_in($key, $values); 1822 | } 1823 | 1824 | return $this; 1825 | } 1826 | 1827 | /** 1828 | * A wrapper to $this->_database->or_where_not_in() 1829 | */ 1830 | public function or_where_not_in($key = NULL, $values = NULL, $escape = NULL) 1831 | { 1832 | if ($this->_is_ci_3) 1833 | { 1834 | $this->_database->or_where_not_in($key, $values, $escape); 1835 | } 1836 | else 1837 | { 1838 | $this->_database->or_where_not_in($key, $values); 1839 | } 1840 | 1841 | return $this; 1842 | } 1843 | 1844 | /** 1845 | * A wrapper to $this->_database->like() 1846 | */ 1847 | public function like($field, $match = '', $side = 'both', $escape = NULL) 1848 | { 1849 | if ($this->_is_ci_3) 1850 | { 1851 | $this->_database->like($field, $match, $side, $escape); 1852 | } 1853 | else 1854 | { 1855 | $this->_database->like($field, $match, $side); 1856 | } 1857 | 1858 | return $this; 1859 | } 1860 | 1861 | /** 1862 | * A wrapper to $this->_database->not_like() 1863 | */ 1864 | public function not_like($field, $match = '', $side = 'both', $escape = NULL) 1865 | { 1866 | if ($this->_is_ci_3) 1867 | { 1868 | $this->_database->not_like($field, $match, $side, $escape); 1869 | } 1870 | else 1871 | { 1872 | $this->_database->not_like($field, $match, $side); 1873 | } 1874 | 1875 | return $this; 1876 | } 1877 | 1878 | /** 1879 | * A wrapper to $this->_database->or_like() 1880 | */ 1881 | public function or_like($field, $match = '', $side = 'both', $escape = NULL) 1882 | { 1883 | if ($this->_is_ci_3) 1884 | { 1885 | $this->_database->or_like($field, $match, $side, $escape); 1886 | } 1887 | else 1888 | { 1889 | $this->_database->or_like($field, $match, $side); 1890 | } 1891 | 1892 | return $this; 1893 | } 1894 | 1895 | /** 1896 | * A wrapper to $this->_database->or_not_like() 1897 | */ 1898 | public function or_not_like($field, $match = '', $side = 'both', $escape = NULL) 1899 | { 1900 | if ($this->_is_ci_3) 1901 | { 1902 | $this->_database->or_not_like($field, $match, $side, $escape); 1903 | } 1904 | else 1905 | { 1906 | $this->_database->or_not_like($field, $match, $side); 1907 | } 1908 | 1909 | return $this; 1910 | } 1911 | 1912 | /** 1913 | * A wrapper to $this->_database->group_start() 1914 | */ 1915 | public function group_start($not = '', $type = 'AND ') 1916 | { 1917 | if ($this->_is_ci_3) 1918 | { 1919 | $this->_database->group_start($not, $type); 1920 | } 1921 | else 1922 | { 1923 | die('DB::group_start() is not supported. Use CodeIgniter 3.0.0 or higher.'); 1924 | } 1925 | 1926 | return $this; 1927 | } 1928 | 1929 | /** 1930 | * A wrapper to $this->_database->or_group_start() 1931 | */ 1932 | public function or_group_start() 1933 | { 1934 | if ($this->_is_ci_3) 1935 | { 1936 | $this->_database->or_group_start(); 1937 | } 1938 | else 1939 | { 1940 | die('DB::or_group_start() is not supported. Use CodeIgniter 3.0.0 or higher.'); 1941 | } 1942 | 1943 | return $this; 1944 | } 1945 | 1946 | /** 1947 | * A wrapper to $this->_database->not_group_start() 1948 | */ 1949 | public function not_group_start() 1950 | { 1951 | if ($this->_is_ci_3) 1952 | { 1953 | $this->_database->not_group_start(); 1954 | } 1955 | else 1956 | { 1957 | die('DB::not_group_start() is not supported. Use CodeIgniter 3.0.0 or higher.'); 1958 | } 1959 | 1960 | return $this; 1961 | } 1962 | 1963 | /** 1964 | * A wrapper to $this->_database->or_not_group_start() 1965 | */ 1966 | public function or_not_group_start() 1967 | { 1968 | if ($this->_is_ci_3) 1969 | { 1970 | $this->_database->or_not_group_start(); 1971 | } 1972 | else 1973 | { 1974 | die('DB::or_not_group_start() is not supported. Use CodeIgniter 3.0.0 or higher.'); 1975 | } 1976 | 1977 | return $this; 1978 | } 1979 | 1980 | /** 1981 | * A wrapper to $this->_database->group_end() 1982 | */ 1983 | public function group_end() 1984 | { 1985 | if ($this->_is_ci_3) 1986 | { 1987 | $this->_database->group_end(); 1988 | } 1989 | else 1990 | { 1991 | die('DB::group_end() is not supported. Use CodeIgniter 3.0.0 or higher.'); 1992 | } 1993 | 1994 | return $this; 1995 | } 1996 | 1997 | /** 1998 | * A wrapper to $this->_database->group_by() 1999 | */ 2000 | public function group_by($by, $escape = NULL) 2001 | { 2002 | if ($this->_is_ci_3) 2003 | { 2004 | $this->_database->group_by($by, $escape); 2005 | } 2006 | else 2007 | { 2008 | $this->_database->group_by($by); 2009 | } 2010 | 2011 | return $this; 2012 | } 2013 | 2014 | /** 2015 | * A wrapper to $this->_database->having() 2016 | */ 2017 | public function having($key, $value = NULL, $escape = NULL) 2018 | { 2019 | $escape = $this->_check_default_escape($escape); 2020 | $this->_database->having($key, $value, $escape); 2021 | return $this; 2022 | } 2023 | 2024 | /** 2025 | * A wrapper to $this->_database->or_having() 2026 | */ 2027 | public function or_having($key, $value = NULL, $escape = NULL) 2028 | { 2029 | $escape = $this->_check_default_escape($escape); 2030 | $this->_database->having($key, $value, $escape); 2031 | return $this; 2032 | } 2033 | 2034 | /** 2035 | * A wrapper to $this->_database->table_exists() 2036 | */ 2037 | public function table_exists($table_name = NULL) 2038 | { 2039 | $table_name = (string) $table_name; 2040 | 2041 | if ($table_name == '') 2042 | { 2043 | $table_name = $this->_table; 2044 | } 2045 | 2046 | if (!isset($this->_database) || !is_object($this->_database)) 2047 | { 2048 | return FALSE; 2049 | } 2050 | 2051 | return $this->_database->table_exists($table_name); 2052 | } 2053 | 2054 | /** 2055 | * A wrapper to CodeIgniter 3 $this->_database->reset_query() 2056 | */ 2057 | public function reset_query() { 2058 | 2059 | if ($this->_is_ci_3) { 2060 | $this->_database->reset_query(); 2061 | } 2062 | 2063 | $this->_reset_state(); 2064 | 2065 | return $this; 2066 | } 2067 | 2068 | /* -------------------------------------------------------------- 2069 | * INTERNAL METHODS 2070 | * ------------------------------------------------------------ */ 2071 | 2072 | /** 2073 | * Trigger an event and call its observers. Pass through the event name 2074 | * (which looks for an instance variable $this->event_name), an array of 2075 | * parameters to pass through and an optional 'last in interation' boolean 2076 | */ 2077 | public function trigger($event, $data = FALSE, $last = TRUE) 2078 | { 2079 | if (!$this->_temporary_skip_observers && isset($this->$event) && is_array($this->$event)) 2080 | { 2081 | foreach ($this->$event as $method) 2082 | { 2083 | if (strpos((string) $method, '(')) 2084 | { 2085 | preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', (string) $method, $matches); 2086 | 2087 | $method = $matches[1]; 2088 | $this->callback_parameters = explode(',', $matches[3]); 2089 | } 2090 | 2091 | $data = call_user_func_array(array($this, $method), array($data, $last)); 2092 | } 2093 | } 2094 | 2095 | return $data; 2096 | } 2097 | 2098 | /** 2099 | * Run validation on the passed data 2100 | */ 2101 | public function validate($data) 2102 | { 2103 | if ($this->skip_validation) 2104 | { 2105 | return $data; 2106 | } 2107 | 2108 | if (!empty($this->validate)) 2109 | { 2110 | foreach($data as $key => $val) 2111 | { 2112 | $_POST[$key] = $val; 2113 | } 2114 | 2115 | $this->load->library('form_validation'); 2116 | 2117 | if (is_array($this->validate)) 2118 | { 2119 | $this->form_validation->set_rules($this->validate); 2120 | 2121 | if ($this->form_validation->run() === TRUE) 2122 | { 2123 | return $data; 2124 | } 2125 | else 2126 | { 2127 | return FALSE; 2128 | } 2129 | } 2130 | else 2131 | { 2132 | if ($this->form_validation->run($this->validate) === TRUE) 2133 | { 2134 | return $data; 2135 | } 2136 | else 2137 | { 2138 | return FALSE; 2139 | } 2140 | } 2141 | } 2142 | else 2143 | { 2144 | return $data; 2145 | } 2146 | } 2147 | 2148 | /** 2149 | * Guess the table name by pluralising the model name 2150 | */ 2151 | private function _fetch_table() 2152 | { 2153 | if ($this->_table == NULL) 2154 | { 2155 | $this->_table = plural(preg_replace('/(_m|_model)?$/', '', strtolower(get_class($this)))); 2156 | } 2157 | } 2158 | 2159 | /** 2160 | * Establish the database connection. 2161 | */ 2162 | private function _set_database() 2163 | { 2164 | if (!class_exists('CI_DB', FALSE)) 2165 | { 2166 | // There is no connection. Skip silently. 2167 | // Possibly specific requests do not require database connection. 2168 | return; 2169 | } 2170 | 2171 | // Was a DB group specified by the user? 2172 | if (isset($this->_db_group)) 2173 | { 2174 | $this->_database = $this->load->database($this->_db_group, TRUE, TRUE); 2175 | } 2176 | // No DB group specified, use the default connection. 2177 | else 2178 | { 2179 | $db = @ get_instance()->db; 2180 | 2181 | // Has the default connection been loaded yet? 2182 | if ( ! isset($db) OR ! is_object($db) OR empty($db->conn_id)) 2183 | { 2184 | get_instance()->load->database('', FALSE, TRUE); 2185 | } 2186 | 2187 | $this->_database = get_instance()->db; 2188 | } 2189 | } 2190 | 2191 | /** 2192 | * Set WHERE parameters, cleverly 2193 | */ 2194 | protected function _set_where($params = NULL) 2195 | { 2196 | if (empty($params) || is_object($params)) 2197 | { 2198 | return; 2199 | } 2200 | 2201 | if (count($params) == 1) 2202 | { 2203 | $this->_database->where($params[0]); 2204 | } 2205 | elseif (count($params) == 2) 2206 | { 2207 | $this->_database->where($params[0], $params[1]); 2208 | } 2209 | elseif (count($params) == 3) 2210 | { 2211 | $this->_database->where($params[0], $params[1], $params[2]); 2212 | } 2213 | else 2214 | { 2215 | $this->_database->where($params); 2216 | } 2217 | } 2218 | 2219 | /** 2220 | * Return the method name for the current return type 2221 | */ 2222 | protected function _return_type($multi = FALSE) 2223 | { 2224 | $method = ($multi) ? 'result' : 'row'; 2225 | return $this->_temporary_return_type == 'array' ? $method . '_array' : $method; 2226 | } 2227 | 2228 | /** 2229 | * Returns a singe value (the first column) from a given result row. 2230 | */ 2231 | protected function _return_value(& $row) 2232 | { 2233 | $result = NULL; 2234 | 2235 | if (is_array($row)) 2236 | { 2237 | if (!empty($row)) 2238 | { 2239 | reset($row); 2240 | $result = current($row); 2241 | } 2242 | } 2243 | elseif (is_object($row)) 2244 | { 2245 | $row_array = get_object_vars($row); 2246 | if (!empty($row_array)) 2247 | { 2248 | $result = current($row_array); 2249 | } 2250 | } 2251 | 2252 | if ($this->_as_json) 2253 | { 2254 | return $this->_return_json($result); 2255 | } 2256 | 2257 | $this->_reset_state(); 2258 | 2259 | return $result; 2260 | } 2261 | 2262 | protected function _return_json(& $data) 2263 | { 2264 | $as_json_options = $this->_as_json_options; 2265 | 2266 | $this->_reset_state(); 2267 | 2268 | return is_php('5.3.0') ? json_encode($data, $as_json_options) : json_encode($data); 2269 | } 2270 | 2271 | /** 2272 | * Returns a compiled SQL statement based on the current Query Builder state. 2273 | * Also resets the Query Builder state and the internal state of this class. 2274 | */ 2275 | protected function _return_sql($sql_type) 2276 | { 2277 | $this->_reset_state(); 2278 | 2279 | switch ($sql_type) 2280 | { 2281 | case 'select': 2282 | 2283 | return $this->_database->get_compiled_select($this->_table, true); 2284 | 2285 | case 'insert': 2286 | 2287 | return $this->_database->get_compiled_insert($this->_table, true); 2288 | 2289 | case 'update': 2290 | 2291 | return $this->_database->get_compiled_update($this->_table, true); 2292 | 2293 | case 'delete': 2294 | 2295 | return $this->_database->get_compiled_delete($this->_table, true); 2296 | 2297 | case 'truncate': 2298 | 2299 | if ($this->_is_ci_3) 2300 | { 2301 | return 'TRUNCATE '.$this->_database->protect_identifiers($this->_table, TRUE, NULL, FALSE); 2302 | } 2303 | 2304 | return 'TRUNCATE '.$this->_database->protect_identifiers($this->_table, TRUE); 2305 | } 2306 | 2307 | return NULL; 2308 | } 2309 | 2310 | /** 2311 | * Returns CI major version dependent default value for the $escape parameter. 2312 | * As of CI 3.0.0 the QB's method definition has been changed into: 2313 | * public function set($key, $value = '', $escape = NULL) 2314 | */ 2315 | protected function _check_default_escape($escape) 2316 | { 2317 | if ($this->_is_ci_3) 2318 | { 2319 | return $escape; 2320 | } 2321 | 2322 | if (is_null($escape)) 2323 | { 2324 | return TRUE; 2325 | } 2326 | 2327 | return $escape; 2328 | } 2329 | 2330 | /** 2331 | * Resets all internal state flags and temporary scope data. 2332 | */ 2333 | protected function _reset_state() 2334 | { 2335 | $this->_with = array(); 2336 | $this->_temporary_return_type = $this->return_type; 2337 | $this->_temporary_with_deleted = FALSE; 2338 | $this->_temporary_only_deleted = FALSE; 2339 | $this->qb_as_value = FALSE; 2340 | $this->qb_as_sql = FALSE; 2341 | $this->qb_distinct = FALSE; 2342 | $this->_as_json = FALSE; 2343 | $this->_as_json_options = 0; 2344 | $this->_temporary_skip_observers = FALSE; 2345 | $this->_select_called = FALSE; 2346 | $this->_limit = FALSE; 2347 | $this->_offset = FALSE; 2348 | } 2349 | 2350 | /** 2351 | * Returns the current user ID. 2352 | */ 2353 | protected function _get_user_id() 2354 | { 2355 | if (is_callable($this->user_id_getter)) 2356 | { 2357 | return is_array($this->user_id_getter) 2358 | ? $this->user_id_getter[0]->{$this->user_id_getter[1]}() 2359 | : call_user_func($this->user_id_getter); 2360 | } 2361 | 2362 | return NULL; 2363 | } 2364 | 2365 | } 2366 | --------------------------------------------------------------------------------