├── .gitignore ├── src ├── MY_Model.php └── CI_Base_Model.php ├── .travis.yml ├── phpunit.xml.dist ├── tests ├── support │ ├── models │ │ ├── record_model.php │ │ ├── protected_attributes_model.php │ │ ├── validated_model.php │ │ ├── CI_My_model.php │ │ ├── serialised_data_model.php │ │ ├── soft_delete_model.php │ │ ├── timestamp_model.php │ │ ├── primary_key_model.php │ │ ├── blamable_model.php │ │ ├── relationship_model.php │ │ ├── callback_parameter_model.php │ │ ├── after_callback_model.php │ │ └── before_callback_model.php │ ├── database.php │ └── test_helper.php └── CI_Base_Model_test.php ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | vendor 4 | composer.lock 5 | composer.phar 6 | phpunit.xml -------------------------------------------------------------------------------- /src/MY_Model.php: -------------------------------------------------------------------------------- 1 | session->userdata('user_id'); 10 | } 11 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | before_script: 9 | - curl -s https://getcomposer.org/installer | php 10 | - php composer.phar install --dev 11 | 12 | script: vendor/bin/phpunit tests/CI_Base_Model_test.php -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | ./tests/CI_Base_Model_test.php 9 | 10 | 11 | 12 | 13 | ./ 14 | 15 | ./tests 16 | ./vendor 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/support/models/record_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class Record_model extends CI_MY_Model { 18 | } -------------------------------------------------------------------------------- /tests/support/models/protected_attributes_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * protected_attributes_model.php contains a test model with protected attributes 19 | */ 20 | 21 | class Protected_attributes_model extends CI_MY_Model 22 | { 23 | public $protected_attributes = array( 'id', 'hash' ); 24 | } -------------------------------------------------------------------------------- /tests/support/models/validated_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class Validated_model extends CI_MY_Model 18 | { 19 | public $validate = array( 20 | array( 'field' => 'name', 'label' => 'Name', 'rules' => 'required' ), 21 | array( 'field' => 'sexyness', 'label' => 'Sexyness', 'rules' => 'required' ) 22 | ); 23 | } -------------------------------------------------------------------------------- /tests/support/models/CI_My_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class CI_MY_Model extends CI_Base_Model { 18 | 19 | protected $primary_key = 'id'; 20 | protected $current_user_id_session_key = 'user_id'; 21 | public $_database; 22 | 23 | protected function get_current_user() 24 | { 25 | return 1; 26 | } 27 | } -------------------------------------------------------------------------------- /tests/support/models/serialised_data_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * serialised_data_model.php contains a test model that includes serialising a columns 19 | */ 20 | 21 | class Serialised_data_model extends CI_MY_Model 22 | { 23 | public $before_create = array( 'serialize_row(data)' ); 24 | public $before_update = array( 'serialize_row(data)' ); 25 | } -------------------------------------------------------------------------------- /tests/support/models/soft_delete_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class Soft_delete_model extends CI_MY_Model 18 | { 19 | protected $deleted_at_key = 'deleted_at'; 20 | 21 | public function __construct($deleted_at = 'deleted_at'){ 22 | $this->deleted_at_key = $deleted_at; 23 | array_push($this->_fields, $deleted_at); 24 | 25 | parent::__construct(); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/support/models/timestamp_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class Timestamp_model extends CI_MY_Model 18 | { 19 | protected $created_at_key = 'created_at'; 20 | protected $updated_at_key = 'updated_at'; 21 | 22 | public function __construct(){ 23 | array_push($this->_fields, $this->created_at_key); 24 | array_push($this->_fields, $this->updated_at_key); 25 | parent::__construct(); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/support/models/primary_key_model.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Md Emran Hasan 9 | * @author Roni Kumar Saha 10 | * @version 2.0 11 | * 12 | * @link http://github.com/jamierumbelow/codeigniter-base-model 13 | * @link https://github.com/phpfour/MY_Model 14 | * @link https://github.com/ronisaha/ci-base-model 15 | * 16 | */ 17 | class Book_model extends CI_MY_Model 18 | { 19 | 20 | protected $_table = 'books'; 21 | 22 | public function __construct($database, $key = NULL) 23 | { 24 | $this->primary_key = $key; 25 | $this->_database = $database; 26 | $this->config = new CI_Config(); 27 | $this->_fetch_primary_key(); 28 | 29 | return $this; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /tests/support/models/blamable_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class Blamable_model extends CI_MY_Model 18 | { 19 | protected $created_at_by = 'created_by'; 20 | protected $updated_at_by = 'updated_by'; 21 | protected $_table = 'records'; 22 | 23 | public function __construct(){ 24 | array_push($this->_fields, $this->created_by_key); 25 | array_push($this->_fields, $this->updated_by_key); 26 | parent::__construct(); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/support/models/relationship_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * relationship_model.php contains a test model that has a belongs_to and has_many relationship 19 | */ 20 | 21 | class Belongs_to_model extends CI_MY_Model 22 | { 23 | public $belongs_to = array( 'author' ); 24 | public $has_many = array( 'comments' ); 25 | } 26 | 27 | class Author_modelCI extends CI_MY_Model 28 | { 29 | protected $_table = 'authors'; 30 | protected $primary_key = 'id'; 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Roni Saha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xiidea/ci-base-model", 3 | "type": "library", 4 | "homepage": "http://ronisaha.github.io/ci-base-model/", 5 | "description": "An extension of CodeIgniter's base Model class for providing a couple handy methods", 6 | "keywords": ["CI_Base_Model", "Codeigniter", "model", "crud"], 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Jamie Rumbelow", 11 | "email": "jamie@jamierumbelow.net", 12 | "homepage": "http://jamierumbelow.net" 13 | }, 14 | { 15 | "name": "Md Emran Hasan", 16 | "email": "phpfour@gmail.com" 17 | }, 18 | { 19 | "name": "Roni Saha", 20 | "email": "roni.cse@gmail.com" 21 | } 22 | ], 23 | "minimum-stability": "dev", 24 | "require": { 25 | "php": ">=5.3.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "3.7.*", 29 | "mockery/mockery": ">=0.7.2" 30 | }, 31 | "autoload": { 32 | "classmap": ["src/CI_Base_Model.php"] 33 | }, 34 | "branch-alias": { 35 | "dev-master": "2.1.x-dev" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/support/models/callback_parameter_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * callback_parameter_model.php contains a test model that defines a callback 19 | * with embedded parameters 20 | */ 21 | 22 | class Callback_parameter_model extends CI_MY_Model 23 | { 24 | public $callback = array('some_callback(some_param,another_param)'); 25 | 26 | public function some_method() 27 | { 28 | $this->trigger('callback'); 29 | } 30 | 31 | protected function some_callback() 32 | { 33 | throw new Callback_Test_Exception($this->callback_parameters); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/support/models/after_callback_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * after_callback_model.php contains a test model that defines every after callback as a function 19 | * that throws an exception. We can then catch that in the tests to ensure callbacks work. 20 | */ 21 | 22 | class After_callback_model extends CI_MY_Model 23 | { 24 | protected $after_create = array('test_throw'); 25 | protected $after_update = array('test_throw'); 26 | protected $after_get = array('test_data_callback', 'test_data_callback_two'); 27 | protected $after_delete = array('test_throw'); 28 | 29 | protected function test_throw($row) 30 | { 31 | throw new Callback_Test_Exception($row); 32 | } 33 | 34 | protected function test_data_callback($row) 35 | { 36 | $row['key'] = 'Value'; 37 | return $row; 38 | } 39 | 40 | protected function test_data_callback_two($row) 41 | { 42 | $row['another_key'] = '123 Value'; 43 | return $row; 44 | } 45 | } -------------------------------------------------------------------------------- /tests/support/models/before_callback_model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * before_callback_model.php contains a test model that defines every before callback as a function 19 | * that throws an exception. We can then catch that in the tests to ensure callbacks work. 20 | */ 21 | 22 | class Before_callback_model extends CI_MY_Model 23 | { 24 | protected $before_create = array('test_data_callback', 'test_data_callback_two'); 25 | protected $before_update = array('test_data_callback', 'test_data_callback_two'); 26 | protected $before_get = array('test_throw'); 27 | protected $before_delete = array('test_throw'); 28 | 29 | protected function test_throw($row) 30 | { 31 | throw new Callback_Test_Exception($row); 32 | } 33 | 34 | protected function test_data_callback($row) 35 | { 36 | $row['key'] = 'Value'; 37 | return $row; 38 | } 39 | 40 | protected function test_data_callback_two($row) 41 | { 42 | $row['another_key'] = '123 Value'; 43 | return $row; 44 | } 45 | } -------------------------------------------------------------------------------- /tests/support/database.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * database.php is a fakeified CodeIgniter query builder 19 | */ 20 | 21 | class MY_Model_Mock_DB 22 | { 23 | /** 24 | * CI_DB 25 | */ 26 | public static $prefix = ''; 27 | public function select() { } 28 | public function where() { } 29 | public function where_in() { } 30 | public function get() { } 31 | public function from() { } 32 | public function insert() { } 33 | public function insert_id() { } 34 | public function set() { } 35 | public function update() { } 36 | public function delete() { } 37 | public function order_by() { } 38 | public function limit() { } 39 | public function count_all_results() { } 40 | public function count_all() { } 41 | public function truncate() { } 42 | public function query() { } 43 | public function num_rows() { } 44 | public function affected_rows() { } 45 | public function insert_batch() { } 46 | public function update_batch() { } 47 | public function dbprefix($t = '') { return self::$prefix . $t; } 48 | public function escape($v) {return $v; } 49 | public function list_fields() {return array(); } 50 | public function last_query() {return 'the_last_query'; } 51 | public function insert_string() {return 'the_insert_string'; } 52 | 53 | /** 54 | * CI_DB_Result 55 | */ 56 | public function row() { } 57 | public function result() { } 58 | public function row_array() { } 59 | public function result_array() { } 60 | } -------------------------------------------------------------------------------- /tests/support/test_helper.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | /** 18 | * test_helper.php is the bootstrap file for our tests - it loads up an 19 | * appropriate faux-CodeIgniter environment for our tests to run in. 20 | */ 21 | 22 | require_once 'vendor/autoload.php'; 23 | 24 | require_once 'tests/support/database.php'; 25 | 26 | 27 | /** 28 | * Fake the CodeIgniter base model! 29 | */ 30 | class CI_Model 31 | { 32 | public function __construct() 33 | { 34 | $this->load = new CI_Loader(); 35 | 36 | // Pretend CI has a loaded DB already. 37 | $this->db = new MY_Model_Mock_DB(); 38 | $this->config = new CI_Config(); 39 | } 40 | 41 | public function __get($method) { } 42 | } 43 | 44 | /** 45 | * The loads happen in the constructor (before we can mock anything out), 46 | * so instead we'll fakeify the Loader 47 | */ 48 | class CI_Loader 49 | { 50 | public function __call($method, $params = array()) {} 51 | } 52 | 53 | /** 54 | * ...but relationships load models, so fake that 55 | */ 56 | class MY_Model_Mock_Loader 57 | { 58 | public function model($name, $assigned_name = '') { } 59 | } 60 | 61 | /** 62 | * We also need to fake the inflector 63 | */ 64 | function singular($name) 65 | { 66 | return 'comment'; 67 | } 68 | 69 | function plural($name) 70 | { 71 | return 'records'; 72 | } 73 | 74 | function camelize($str) 75 | { 76 | $str = 'x'.strtolower(trim($str)); 77 | $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); 78 | return substr(str_replace(' ', '', $str), 1); 79 | } 80 | 81 | /** 82 | * Let our tests know about our callbacks 83 | */ 84 | 85 | class MY_Model_Test_Exception extends Exception 86 | { 87 | public $passed_object = FALSE; 88 | 89 | public function __construct($passed_object, $message = '') 90 | { 91 | parent::__construct($message); 92 | $this->passed_object = $passed_object; 93 | } 94 | } 95 | 96 | class Callback_Test_Exception extends MY_Model_Test_Exception 97 | { 98 | public function __construct($passed_object) 99 | { 100 | parent::__construct($passed_object, 'Callback is being successfully thrown'); 101 | } 102 | } 103 | 104 | class CI_Config 105 | { 106 | public function item() 107 | { 108 | return 'MY_'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CI_Base_Model 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/ronisaha/ci-base-model.png?branch=master)](https://travis-ci.org/ronisaha/ci-base-model) 5 | [![Latest Stable Version](https://poser.pugx.org/xiidea/ci-base-model/v/stable.png)](https://packagist.org/packages/xiidea/ci-base-model) 6 | [![Total Downloads](https://poser.pugx.org/xiidea/ci-base-model/downloads.png)](https://packagist.org/packages/xiidea/ci-base-model) 7 | 8 | An extension of CodeIgniter's base Model class to remove repetition and increase productivity by providing a couple handy methods. Most of the functionality 9 | of CI_Base_Model taken from following two library 10 | * [http://github.com/jamierumbelow/codeigniter-base-model](http://github.com/jamierumbelow/codeigniter-base-model) 11 | * [https://github.com/phpfour/MY_Model](https://github.com/phpfour/MY_Model) 12 | 13 | 14 | Key Features 15 | ============ 16 | * Basic CRUD Functionality 17 | * validation-in-model 18 | * Simple Event Callback system 19 | * Blamable 20 | * Soft-Deletable 21 | * Timestampable 22 | * Multiple database group support 23 | * Easy to use 24 | * Support both CamelCase and underscore version of a function (you can use findAll/find_all both will do the same). As codeigniter's convention the library implemented in underscore version of the functions 25 | 26 | 27 | Synopsis 28 | -------- 29 | 30 | ```php 31 | class Post_model extends MY_Model { } 32 | 33 | $this->load->model('post_model', 'post'); 34 | 35 | $this->post->get_all(); 36 | 37 | $this->post->get(1); 38 | $this->post->get_by('title', 'Pigs CAN Fly!'); 39 | $this->post->get_many_by('status', 'open'); 40 | //or $this->post->getManyBy('status', 'open'); 41 | 42 | $this->post->insert(array( 43 | 'status' => 'open', 44 | 'title' => "I'm too sexy for my shirt" 45 | )); 46 | 47 | $this->post->update(1, array( 'status' => 'closed' )); 48 | 49 | $this->post->delete(1); 50 | ``` 51 | 52 | Installation/Usage 53 | ------------------ 54 | 55 | Download and copy the MY\_Model.php and CI\_Base\_Model.php file into your _application/core_ folder. CodeIgniter will load and initialise this class automatically for you. 56 | 57 | Extend your model classes from `MY_Model` and all the functionality will be baked in automatically. You may wondering why we use two file? Here are the benefits. 58 | * You can implement all your global implementation in MY_Model. 59 | * You can update CI_Base_Model any time. Your own global implementation will not affected. 60 | 61 | **Note:** The **MY\_** prefix is the default prefix used to extend a class in CodeIgniter. If you have modified this in your **_application/config/config.php**, use your prefix as appropriate. and modify the MY_Model class 62 | 63 | Naming Conventions 64 | ------------------ 65 | 66 | This class will try to guess the name of the table to use, by finding the plural of the class name. 67 | 68 | For instance: 69 | 70 | class Post_model extends MY_Model { } 71 | 72 | ...will guess a table name of `posts`. It also works with `_m`: 73 | 74 | class Book_m extends MY_Model { } 75 | 76 | ...will guess `books`. 77 | 78 | If you need to set it to something else, you can declare the _$\_table_ instance variable and set it to the table name: 79 | 80 | class Post_model extends MY_Model 81 | { 82 | public $_table = 'blogposts'; 83 | } 84 | 85 | Some of the CRUD functions will try to guess your primary key ID column. You can overwrite this functionality by setting the _$primary\_key_ instance variable: 86 | 87 | class Post_model extends MY_Model 88 | { 89 | public $primary_key = 'post_id'; 90 | } 91 | 92 | Callbacks/Observers 93 | ------------------- 94 | 95 | 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, **CI_Base_Model** contains a series of callbacks/observers -- methods that will be called at certain points. 96 | 97 | The default list of observers are as follows: 98 | 99 | * $before_create 100 | * $after_create 101 | * $before_update 102 | * $after_update 103 | * $before_get 104 | * $after_get 105 | * $before_delete 106 | * $after_delete 107 | 108 | 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: 109 | 110 | ```php 111 | class Book_model extends MY_Model 112 | { 113 | public $before_create = array( 'timestamps' ); 114 | 115 | protected function timestamps($book) 116 | { 117 | $book['created_at'] = $book['updated_at'] = date('Y-m-d H:i:s'); 118 | return $book; 119 | } 120 | } 121 | ``` 122 | 123 | **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.** 124 | 125 | Observers can also take parameters in their name, much like CodeIgniter's Form Validation library. Parameters are then accessed in `$this->callback_parameters`: 126 | 127 | public $before_create = array( 'data_process(name)' ); 128 | public $before_update = array( 'data_process(date)' ); 129 | 130 | protected function data_process($row) 131 | { 132 | $row[$this->callback_parameters[0]] = $this->_process($row[$this->callback_parameters[0]]); 133 | 134 | return $row; 135 | } 136 | 137 | Validation 138 | ---------- 139 | 140 | MY_Model uses CodeIgniter's built in form validation to validate data on insert. 141 | 142 | You can enable validation by setting the `$validate` instance to the usual form validation library rules array: 143 | 144 | class User_model extends MY_Model 145 | { 146 | public $validate = array( 147 | array( 'field' => 'email', 148 | 'label' => 'email', 149 | 'rules' => 'required|valid_email|is_unique[users.email]' ), 150 | array( 'field' => 'password', 151 | 'label' => 'password', 152 | 'rules' => 'required' ), 153 | array( 'field' => 'password_confirmation', 154 | 'label' => 'confirm password', 155 | 'rules' => 'required|matches[password]' ), 156 | ); 157 | } 158 | 159 | 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). 160 | 161 | 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.** 162 | 163 | You can skip the validation with `skip_validation()`: 164 | 165 | $this->user_model->skip_validation(); 166 | $this->user_model->insert(array( 'email' => 'blah' )); 167 | 168 | Alternatively, pass through a `TRUE` to `insert()`: 169 | 170 | $this->user_model->insert(array( 'email' => 'blah' ), TRUE); 171 | 172 | Under the hood, this calls `validate()`. 173 | 174 | Protected Attributes 175 | -------------------- 176 | 177 | 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. 178 | 179 | To prevent this from happening, MY_Model supports protected attributes. These are columns of data that cannot be modified. 180 | 181 | You can set protected attributes with the `$protected_attributes` array: 182 | 183 | class Post_model extends MY_Model 184 | { 185 | public $protected_attributes = array( 'id', 'hash' ); 186 | } 187 | 188 | Now, when `update` is called, the attributes will automatically be removed from the array, and, thus, protected: 189 | 190 | $this->post_model->update(1, array( 191 | 'id' => 2, 192 | 'hash' => 'aqe3fwrga23fw243fWE', 193 | 'title' => 'A new post' 194 | )); 195 | 196 | // SQL: INSERT INTO posts (title) VALUES ('A new post') 197 | 198 | Relationships 199 | ------------- 200 | 201 | **MY\_Model** now has support for basic _belongs\_to_ and has\_many relationships. These relationships are easy to define: 202 | 203 | class Post_model extends MY_Model 204 | { 205 | public $belongs_to = array( 'author' ); 206 | public $has_many = array( 'comments' ); 207 | } 208 | 209 | 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: 210 | 211 | class Author_model extends MY_Model { } 212 | class Comment_model extends MY_Model { } 213 | 214 | If you'd like to customise this, you can pass through the model name as a parameter: 215 | 216 | class Post_model extends MY_Model 217 | { 218 | public $belongs_to = array( 'author' => array( 'model' => 'author_m' ) ); 219 | public $has_many = array( 'comments' => array( 'model' => 'model_comments' ) ); 220 | } 221 | 222 | You can then access your related data using the `with()` method: 223 | 224 | $post = $this->post_model->with('author') 225 | ->with('comments') 226 | ->get(1); 227 | 228 | The related data will be embedded in the returned value from `get`: 229 | 230 | echo $post->author->name; 231 | 232 | foreach ($post->comments as $comment) 233 | { 234 | echo $message; 235 | } 236 | 237 | Separate queries will be run to select the data, so where performance is important, a separate JOIN and SELECT call is recommended. 238 | 239 | The primary key can also be configured. For _belongs\_to_ calls, the related key is on the current object, not the foreign one. Pseudocode: 240 | 241 | SELECT * FROM authors WHERE id = $post->author_id 242 | 243 | ...and for a _has\_many_ call: 244 | 245 | SELECT * FROM comments WHERE post_id = $post->id 246 | 247 | To change this, use the `primary_key` value when configuring: 248 | 249 | class Post_model extends MY_Model 250 | { 251 | public $belongs_to = array( 'author' => array( 'primary_key' => 'post_author_id' ) ); 252 | public $has_many = array( 'comments' => array( 'primary_key' => 'parent_post_id' ) ); 253 | } 254 | 255 | Arrays vs Objects 256 | ----------------- 257 | 258 | 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. 259 | 260 | If you'd like all your calls to use the array methods, you can set the `$return_type` variable to `array`. 261 | 262 | class Book_model extends MY_Model 263 | { 264 | protected $return_type = 'array'; 265 | } 266 | 267 | If you'd like just your _next_ call to return a specific type, there are two scoping methods you can use: 268 | 269 | $this->book_model->as_array() 270 | ->get(1); 271 | $this->book_model->as_object() 272 | ->get_by('column', 'value'); 273 | 274 | Soft Delete 275 | ----------- 276 | 277 | 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'. 278 | 279 | If you enable soft deleting, the deleted row will be marked as `deleted` rather than actually being removed from the database. 280 | 281 | Take, for example, a `Book_model`: 282 | 283 | class Book_model extends MY_Model { } 284 | 285 | We can enable soft delete by setting the `$this->deleted_at_key` key: 286 | 287 | class Book_model extends MY_Model 288 | { 289 | protected $deleted_at_key = 'deleted_at'; 290 | } 291 | 292 | By default, MY_Model expects a `Datetime` or `TIMESTAMP` column named `deleted_at`. If you'd like to customise this, you can set `$deleted_at_key`: 293 | 294 | class Book_model extends MY_Model 295 | { 296 | protected $deleted_at_key = 'book_deleted_at'; 297 | } 298 | 299 | If you wish to track the deleted by you can set `$deleted_by_key` member, 300 | 301 | class Book_model extends MY_Model 302 | { 303 | protected $deleted_at_key = 'book_deleted_at'; 304 | protected $deleted_by_key = 'book_deleted_by'; 305 | } 306 | 307 | Now, when you make a call to any of the `get_` methods, a constraint will be added to not withdraw deleted columns: 308 | 309 | => $this->book_model->get_by('user_id', 1); 310 | -> SELECT * FROM books WHERE user_id = 1 AND deleted < NOW() 311 | 312 | If you'd like to include deleted columns, you can use the `with_deleted()` scope: 313 | 314 | => $this->book_model->with_deleted()->get_by('user_id', 1); 315 | -> SELECT * FROM books WHERE user_id = 1 316 | 317 | If you'd like to include only the columns that have been deleted, you can use the `only_deleted()` scope: 318 | 319 | => $this->book_model->only_deleted()->get_by('user_id', 1); 320 | -> SELECT * FROM books WHERE user_id = 1 AND deleted >= NOW() 321 | 322 | You can delete in future!! 323 | 324 | => $this->book_model->delete_at(1, (new \DateTime())->modify('+1 day')); 325 | 326 | Blamable 327 | -------- 328 | Take, for example, a `Book_model`: 329 | 330 | class Book_model extends MY_Model { } 331 | 332 | We can enable blamable by setting the `$this->created_by_key` abd `$this->updated_by_key` key. And you need to implement the get_current_user() function, in the MY_Model 333 | 334 | class Book_model extends MY_Model 335 | { 336 | protected $created_by_key = 'created_by'; 337 | protected $updated_by_key = 'updated_by'; 338 | } 339 | 340 | class MY_Model extends CI_Base_Model{ 341 | protected $current_user_id_session_key = 'user_id'; 342 | } 343 | 344 | Now, when you make a call to any of the `insert`, `update`, `update_` methods the Model will automatically insert/update the created_by/updated_by entry 345 | 346 | => $this->book_model->insert(array('title' => 'A new book')); 347 | -> SQL: INSERT INTO books (title, updated_by) VALUES ('A new post', 1) //Assuming current user id is 1 348 | 349 | 350 | Built-in Observers 351 | ------------------- 352 | 353 | **CI_Base_Model** contains a few built-in observers. The timestamps (MySQL compatible) `created_at` and `updated_at` are now available as built-in observers: 354 | 355 | class Post_model extends MY_Model 356 | { 357 | public $before_create = array( 'created_at', 'updated_at' ); 358 | public $before_update = array( 'updated_at' ); 359 | } 360 | 361 | **CI_Base_Model** also contains serialisation observers for serialising and unserializing 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: 362 | 363 | class Event_model extends MY_Model 364 | { 365 | public $before_create = array( 'serialize_row(seat_types)' ); 366 | public $before_update = array( 'serialize_row(seat_types)' ); 367 | public $after_get = array( 'unserialize_row(seat_types)' ); 368 | } 369 | 370 | Database Connection 371 | ------------------- 372 | 373 | The class will automatically use the default database connection, and even load it for you if you haven't yet. 374 | 375 | You can specify a database connection on a per-model basis by declaring the _$\_database\_group_ . 376 | 377 | See ["Connecting to your Database"](http://ellislab.com/codeigniter/user-guide/database/connecting.html) for more information. 378 | 379 | ```php 380 | class Post_model extends MY_Model 381 | { 382 | protected $_database_group = 'group_name'; 383 | } 384 | ``` 385 | 386 | Methods 387 | ========= 388 | 389 | * find_by($field, $value, $fields, $order) [alias findBy] 390 | * find_by_{$field}($value, $fields, $order) [alias findBy{$field}] 391 | * find_all_by($field, $value, $fields, $order, $start, $limit) [alias findAllBy] 392 | * find_all_by_{$field}($value, $fields, $order, $start, $limit) [alias findAllBy{$field}] 393 | * find_field_by($field, $value, $fields = '*', $order = NULL) [alias findFieldBy] 394 | * find_field_by_{$field}($value, $fields = '*', $order = NULL) [alias findFieldBy{$field}] 395 | * find_all($conditions, $fields, $order, $start, $limit) [alias findAll] 396 | * find($conditions, $fields, $order) 397 | * field($conditions, $name, $fields, $order) 398 | * get($id) 399 | * get_all() 400 | * get_by() 401 | * get_many(array $primary_values) 402 | * get_many_by() 403 | * find_count($conditions) [alias findCount] 404 | * insert($data, $skip_validation = FALSE) 405 | * insert_many($data, $skip_validation = FALSE, $insert_individual = false) 406 | * update($primary_value, $data, $skip_validation = FALSE) 407 | * update_many($primary_values, $data, $skip_validation = FALSE) 408 | * update_by() 409 | * update_all($data) 410 | * update_batch($data, $where_key) 411 | * on_duplicate_update($data, $update) 412 | * delete($id) 413 | * delete_by() //argument can be in any form supported by $this->db->where(); 414 | * delete_many(array $primary_values) [alias deleteMany] 415 | * delete_at($id, $time) [alias deleteAt] 416 | * delete_by_at($condition, $time) [alias deleteByAt] 417 | * delete_many_at(array $primary_values, $time) [alias deleteManyAt] 418 | * execute_query($query) [alias executeQuery] 419 | * order_by($orders) [alias orderBy] 420 | * dropdown() [can be called dropdown($name_field) or dropdown($key_field, $name_field)] 421 | * subscribe($event, $observer, $handler_name) 422 | * is_subscribed($event, $handler_name) 423 | 424 | 425 | Unit Tests 426 | ---------- 427 | 428 | MY_Model contains a robust set of unit tests to ensure that the system works as planned. 429 | 430 | Install the testing framework (PHPUnit) with Composer: 431 | 432 | $ curl -s https://getcomposer.org/installer | php 433 | $ php composer.phar install 434 | 435 | You can then run the tests using the `vendor/bin/phpunit` binary and specify the tests file: 436 | 437 | $ vendor/bin/phpunit 438 | 439 | 440 | Contributing to CI_Base_Model 441 | ------------------------ 442 | 443 | If you find a bug or want to add a feature to CI_Base_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: 444 | 445 | 1. Fork the project. 446 | 2. **Branch out into a new branch. `git checkout -b name_of_new_feature_or_bug`** 447 | 3. Make your feature addition or bug fix. 448 | 4. **Add tests for it. This is important so I don’t break it in a future version unintentionally.** 449 | 5. Commit. 450 | 6. Send me a pull request! 451 | -------------------------------------------------------------------------------- /tests/CI_Base_Model_test.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.0 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | use Mockery as m; 18 | 19 | require_once 'tests/support/test_helper.php'; 20 | require_once 'tests/support/models/CI_My_model.php'; 21 | require_once 'tests/support/models/after_callback_model.php'; 22 | require_once 'tests/support/models/before_callback_model.php'; 23 | require_once 'tests/support/models/blamable_model.php'; 24 | require_once 'tests/support/models/callback_parameter_model.php'; 25 | require_once 'tests/support/models/primary_key_model.php'; 26 | require_once 'tests/support/models/protected_attributes_model.php'; 27 | require_once 'tests/support/models/record_model.php'; 28 | require_once 'tests/support/models/relationship_model.php'; 29 | require_once 'tests/support/models/serialised_data_model.php'; 30 | require_once 'tests/support/models/soft_delete_model.php'; 31 | require_once 'tests/support/models/timestamp_model.php'; 32 | require_once 'tests/support/models/validated_model.php'; 33 | 34 | class CI_Base_Model_tests extends PHPUnit_Framework_TestCase 35 | { 36 | public $model; 37 | 38 | /* -------------------------------------------------------------- 39 | * TEST INFRASTRUCTURE 40 | * ------------------------------------------------------------ */ 41 | 42 | public function setUp() 43 | { 44 | $this->model = new Record_model(); 45 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 46 | } 47 | 48 | public function tearDown() 49 | { 50 | unset($this->model); 51 | } 52 | 53 | /* -------------------------------------------------------------- 54 | * GENERIC METHODS 55 | * ------------------------------------------------------------ */ 56 | 57 | public function test_constructor_guesses_the_table_name() 58 | { 59 | $this->model = new Record_model(); 60 | 61 | $this->assertEquals($this->model->get_table(), 'records'); 62 | } 63 | 64 | public function test_constructor_guess_the_primary_key() 65 | { 66 | $this->model = new Book_model($this->model->_database, 'id'); 67 | 68 | $this->model->_database->expects($this->once()) 69 | ->method('query') 70 | ->with($this->equalTo("SHOW KEYS FROM `books` WHERE Key_name = 'PRIMARY'")) 71 | ->will($this->returnValue($this->model->_database)) 72 | ; 73 | 74 | $this->model->_database->expects($this->once()) 75 | ->method('dbprefix') 76 | ->with($this->equalTo('books')) 77 | ->will($this->returnValue('books')) 78 | ; 79 | 80 | $this->model->_database->expects($this->once()) 81 | ->method('row') 82 | ->will($this->returnValue((object)array('Column_name'=>'fake_primary_key'))); 83 | 84 | $this->model = $this->model->__construct($this->model->_database); 85 | 86 | $this->assertEquals($this->model->primary_key(), 'fake_primary_key'); 87 | } 88 | 89 | /* -------------------------------------------------------------- 90 | * CRUD INTERFACE 91 | * ------------------------------------------------------------ */ 92 | 93 | public function test_get() 94 | { 95 | $this->model->_database->expects($this->once()) 96 | ->method('where') 97 | ->with($this->equalTo('id'), $this->equalTo(2)) 98 | ->will($this->returnValue($this->model->_database)); 99 | $this->_expect_get(); 100 | $this->model->_database->expects($this->once()) 101 | ->method('row') 102 | ->will($this->returnValue('fake_record_here')); 103 | 104 | $this->assertEquals($this->model->get(2), 'fake_record_here'); 105 | } 106 | 107 | public function test_get_by() 108 | { 109 | $this->_mock_get_by_call(); 110 | 111 | $this->assertEquals($this->model->get_by('some_column', 'some_value'), 'fake_record_here'); 112 | } 113 | 114 | public function test_get_by_alias() 115 | { 116 | $this->_mock_get_by_call(); 117 | 118 | $this->assertEquals($this->model->getBy('some_column', 'some_value'), 'fake_record_here'); 119 | } 120 | 121 | public function test_get_many() 122 | { 123 | $this->model->_database->expects($this->once()) 124 | ->method('where_in') 125 | ->with($this->equalTo('id'), $this->equalTo(array(1, 2, 3, 4, 5))) 126 | ->will($this->returnValue($this->model->_database)); 127 | $this->_expect_get(); 128 | $this->model->_database->expects($this->once()) 129 | ->method('result') 130 | ->will($this->returnValue(array('fake', 'records', 'here'))); 131 | 132 | $this->assertEquals($this->model->get_many(array(1, 2, 3, 4, 5)), array('fake', 'records', 'here')); 133 | } 134 | 135 | public function test_get_many_by() 136 | { 137 | $this->model->_database->expects($this->once()) 138 | ->method('where') 139 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 140 | ->will($this->returnValue($this->model->_database)); 141 | $this->_expect_get(); 142 | $this->model->_database->expects($this->once()) 143 | ->method('result') 144 | ->will($this->returnValue(array('fake', 'records', 'here'))); 145 | 146 | $this->assertEquals($this->model->get_many_by('some_column', 'some_value'), array('fake', 'records', 'here')); 147 | } 148 | 149 | public function test_get_all() 150 | { 151 | $this->_expect_get(); 152 | $this->model->_database->expects($this->once()) 153 | ->method('result') 154 | ->will($this->returnValue(array('fake', 'records', 'here'))); 155 | 156 | $this->assertEquals($this->model->get_all(), array('fake', 'records', 'here')); 157 | } 158 | 159 | private function _expect_fake_find() 160 | { 161 | $this->model->_database->expects($this->once()) 162 | ->method('where') 163 | ->with($this->equalTo(array('some_column'=>'1'))); 164 | 165 | $this->_expect_get(); 166 | $this->model->_database->expects($this->once()) 167 | ->method('result') 168 | ->will($this->returnValue(array('fake', 'records', 'here'))); 169 | } 170 | 171 | public function test_find() 172 | { 173 | $this->_expect_fake_find(); 174 | $this->assertEquals($this->model->find(array('some_column'=>'1')), 'fake'); 175 | } 176 | 177 | public function test_find_all() 178 | { 179 | $this->_expect_fake_find(); 180 | $this->assertEquals($this->model->find_all(array('some_column'=>'1')), array('fake', 'records', 'here')); 181 | } 182 | 183 | public function test_find_all_in_camel_case_form() 184 | { 185 | $this->_expect_fake_find(); 186 | $this->assertEquals($this->model->findAll(array('some_column'=>'1')), array('fake', 'records', 'here')); 187 | } 188 | 189 | public function test_find_by() 190 | { 191 | $this->_expect_fake_find(); 192 | $this->assertEquals($this->model->find_by('some_column','1'), 'fake'); 193 | } 194 | 195 | public function test_find_by_in_camel_case_form() 196 | { 197 | $this->_expect_fake_find(); 198 | $this->assertEquals($this->model->findBy('some_column','1'), 'fake'); 199 | } 200 | 201 | public function test_find_by_some_column() 202 | { 203 | $this->_expect_fake_find(); 204 | $this->assertEquals($this->model->find_by_some_column('1'), 'fake'); 205 | } 206 | 207 | public function test_find_by_some_column_in_camel_case_form() 208 | { 209 | $this->_expect_fake_find(); 210 | $this->assertEquals($this->model->findBySomeColumn('1'), 'fake'); 211 | } 212 | 213 | public function test_insert() 214 | { 215 | $this->model->_database->expects($this->once()) 216 | ->method('insert') 217 | ->with($this->equalTo('records'), $this->equalTo(array('new' => 'data'))); 218 | $this->model->_database->expects($this->any()) 219 | ->method('insert_id') 220 | ->will($this->returnValue(123)); 221 | 222 | $this->assertEquals($this->model->insert(array('new' => 'data')), 123); 223 | } 224 | 225 | public function test_insert_many_with_individual_insert() 226 | { 227 | $this->model->_database->expects($this->exactly(2)) 228 | ->method('insert') 229 | ->with($this->equalTo('records')); 230 | $this->model->_database->expects($this->any()) 231 | ->method('insert_id') 232 | ->will($this->returnValue(123)); 233 | 234 | $this->assertEquals($this->model->insert_many(array(array('new' => 'data'), array('other' => 'data')), true, true), array(123, 123)); 235 | } 236 | 237 | public function test_insert_many_with_batch_insert() 238 | { 239 | $this->model->_database->expects($this->once()) 240 | ->method('insert_batch') 241 | ->with($this->equalTo('records'), $this->equalTo(array(array('new' => 'data'), array('other' => 'data')))) 242 | ->will($this->returnValue(123)); 243 | 244 | $this->assertEquals($this->model->insert_many(array(array('new' => 'data'), array('other' => 'data'))), 123); 245 | } 246 | 247 | public function test_update() 248 | { 249 | $this->model->_database->expects($this->once()) 250 | ->method('where') 251 | ->with($this->equalTo('id'), $this->equalTo(2)) 252 | ->will($this->returnValue($this->model->_database)); 253 | $this->model->_database->expects($this->once()) 254 | ->method('set') 255 | ->with($this->equalTo(array('new' => 'data'))) 256 | ->will($this->returnValue($this->model->_database)); 257 | $this->model->_database->expects($this->once()) 258 | ->method('update') 259 | ->with($this->equalTo('records')) 260 | ->will($this->returnValue(TRUE)); 261 | 262 | $this->assertEquals($this->model->update(2, array('new' => 'data')), TRUE); 263 | } 264 | 265 | public function test_update_many() 266 | { 267 | $this->model->_database->expects($this->once()) 268 | ->method('where_in') 269 | ->with($this->equalTo('id'), $this->equalTo(array(1, 2, 3, 4, 5))) 270 | ->will($this->returnValue($this->model->_database)); 271 | $this->model->_database->expects($this->once()) 272 | ->method('set') 273 | ->with($this->equalTo(array('new' => 'data'))) 274 | ->will($this->returnValue($this->model->_database)); 275 | $this->model->_database->expects($this->once()) 276 | ->method('update') 277 | ->with($this->equalTo('records')) 278 | ->will($this->returnValue(TRUE)); 279 | 280 | $this->assertEquals($this->model->update_many(array(1, 2, 3, 4, 5), array('new' => 'data')), TRUE); 281 | } 282 | 283 | public function test_update_by() 284 | { 285 | $this->model->_database->expects($this->once()) 286 | ->method('where') 287 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 288 | ->will($this->returnValue($this->model->_database)); 289 | $this->model->_database->expects($this->once()) 290 | ->method('set') 291 | ->with($this->equalTo(array('new' => 'data'))) 292 | ->will($this->returnValue($this->model->_database)); 293 | $this->model->_database->expects($this->once()) 294 | ->method('update') 295 | ->with($this->equalTo('records')) 296 | ->will($this->returnValue(TRUE)); 297 | 298 | $this->assertEquals($this->model->update_by('some_column', 'some_value', array('new' => 'data')), TRUE); 299 | } 300 | 301 | public function test_update_all() 302 | { 303 | $this->model->_database->expects($this->once()) 304 | ->method('set') 305 | ->with($this->equalTo(array('new' => 'data'))) 306 | ->will($this->returnValue($this->model->_database)); 307 | $this->model->_database->expects($this->once()) 308 | ->method('update') 309 | ->with($this->equalTo('records')) 310 | ->will($this->returnValue(TRUE)); 311 | 312 | $this->assertEquals($this->model->update_all(array('new' => 'data')), TRUE); 313 | } 314 | 315 | public function test_update_batch() 316 | { 317 | $this->model->_database->expects($this->once()) 318 | ->method('update_batch') 319 | ->with($this->equalTo('records'), $this->equalTo(array(array('new' => 'data'), array('other' => 'data'))),$this->equalTo('new')) 320 | ->will($this->returnValue(123)); 321 | 322 | $this->assertEquals($this->model->update_batch(array(array('new' => 'data'), array('other' => 'data')), 'new'), 123); 323 | } 324 | 325 | public function test_on_duplicate_update() 326 | { 327 | $data = array('new' => 'data'); 328 | 329 | 330 | $this->model->_database->expects($this->once()) 331 | ->method('escape') 332 | ->with($this->equalTo('data')) 333 | ->will($this->returnValue('data')); 334 | 335 | $this->model->_database->expects($this->once()) 336 | ->method('query') 337 | ->with($this->anything()) 338 | ->will($this->returnValue(123)); 339 | 340 | $this->assertEquals($this->model->on_duplicate_update($data, $data), 123); 341 | } 342 | 343 | public function test_delete() 344 | { 345 | $this->model->_database->expects($this->once()) 346 | ->method('where') 347 | ->with($this->equalTo('id'), $this->equalTo(2)) 348 | ->will($this->returnValue($this->model->_database)); 349 | $this->model->_database->expects($this->once()) 350 | ->method('delete') 351 | ->with($this->equalTo('records')) 352 | ->will($this->returnValue(TRUE)); 353 | 354 | $this->assertEquals($this->model->delete(2), TRUE); 355 | } 356 | 357 | public function test_delete_by() 358 | { 359 | $this->model->_database->expects($this->once()) 360 | ->method('where') 361 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 362 | ->will($this->returnValue($this->model->_database)); 363 | $this->model->_database->expects($this->once()) 364 | ->method('delete') 365 | ->with($this->equalTo('records')) 366 | ->will($this->returnValue(TRUE)); 367 | 368 | $this->assertEquals($this->model->delete_by('some_column', 'some_value'), TRUE); 369 | } 370 | 371 | public function test_delete_many() 372 | { 373 | $this->model->_database->expects($this->once()) 374 | ->method('where_in') 375 | ->with($this->equalTo('id'), array(1, 2, 3, 4, 5)) 376 | ->will($this->returnValue($this->model->_database)); 377 | $this->model->_database->expects($this->once()) 378 | ->method('delete') 379 | ->with($this->equalTo('records')) 380 | ->will($this->returnValue(TRUE)); 381 | 382 | $this->assertEquals($this->model->delete_many(array(1, 2, 3, 4, 5)), TRUE); 383 | } 384 | 385 | /* -------------------------------------------------------------- 386 | * MORE CALLBACK TESTS 387 | * ------------------------------------------------------------ */ 388 | 389 | public function test_before_create_callbacks() 390 | { 391 | $this->model = new Before_callback_model(); 392 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 393 | 394 | $row = array( 'one' => 'ONE', 'two' => 'TWO' ); 395 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 396 | 397 | $this->model->_database->expects($this->once()) 398 | ->method('insert') 399 | ->with($this->equalTo('records'), $this->equalTo($expected_row)); 400 | 401 | $this->model->insert($row); 402 | } 403 | 404 | public function test_after_create_callbacks() 405 | { 406 | $this->model = new After_callback_model(); 407 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 408 | 409 | $this->model->_database->expects($this->once()) 410 | ->method('insert_id') 411 | ->will($this->returnValue(10)); 412 | 413 | $self =& $this; 414 | 415 | $this->assertCallbackIsCalled(function() use ($self) 416 | { 417 | $self->model->insert(array( 'row' => 'here' )); 418 | }, 10); 419 | } 420 | 421 | public function test_before_update_callbacks() 422 | { 423 | $this->model = new Before_callback_model(); 424 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 425 | 426 | $row = array( 'one' => 'ONE', 'two' => 'TWO' ); 427 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 428 | 429 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 430 | $this->model->_database->expects($this->once()) 431 | ->method('set') 432 | ->with($this->equalTo($expected_row)) 433 | ->will($this->returnValue($this->model->_database)); 434 | 435 | $this->model->update(1, $row); 436 | } 437 | 438 | public function test_after_update_callbacks() 439 | { 440 | $this->model = new After_callback_model(); 441 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 442 | 443 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 444 | $this->model->_database->expects($this->once())->method('set')->will($this->returnValue($this->model->_database)); 445 | $this->model->_database->expects($this->once())->method('update')->will($this->returnValue(TRUE)); 446 | 447 | $self =& $this; 448 | 449 | $this->assertCallbackIsCalled(function() use ($self) 450 | { 451 | $self->model->update(1, array( 'row' => 'here' )); 452 | }, array( array( 'row' => 'here' ), true )); 453 | } 454 | 455 | public function test_before_get_callbacks() 456 | { 457 | $this->model = new Before_callback_model(); 458 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 459 | 460 | $self =& $this; 461 | 462 | $this->assertCallbackIsCalled(function() use ($self) 463 | { 464 | $self->model->get(1); 465 | }, NULL); 466 | } 467 | 468 | public function test_after_get_callbacks() 469 | { 470 | $this->model = new After_callback_model(); 471 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 472 | 473 | $db_row = array( 'one' => 'ONE', 'two' => 'TWO' ); 474 | $expected_row = array( 'one' => 'ONE', 'two' => 'TWO', 'key' => 'Value', 'another_key' => '123 Value' ); 475 | 476 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 477 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 478 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($db_row)); 479 | 480 | $this->assertEquals($expected_row, $this->model->get(1)); 481 | } 482 | 483 | public function test_before_delete_callbacks() 484 | { 485 | $this->model = new Before_callback_model(); 486 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 487 | 488 | $self =& $this; 489 | 490 | $this->assertCallbackIsCalled(function() use ($self) 491 | { 492 | $self->model->delete(12); 493 | }, 12); 494 | } 495 | 496 | public function test_after_delete_callbacks() 497 | { 498 | $this->model = new After_callback_model(); 499 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 500 | 501 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 502 | $this->model->_database->expects($this->once())->method('delete')->will($this->returnValue(TRUE)); 503 | 504 | $self =& $this; 505 | 506 | $this->assertCallbackIsCalled(function() use ($self) 507 | { 508 | $self->model->delete(9); 509 | }, TRUE); 510 | } 511 | 512 | public function test_callbacks_support_parameters() 513 | { 514 | $this->model = new Callback_parameter_model(); 515 | 516 | $self =& $this; 517 | $callback_parameters = array( 518 | 'some_param', 'another_param' 519 | ); 520 | 521 | $this->assertCallbackIsCalled(function() use ($self) 522 | { 523 | $self->model->some_method(); 524 | }, $callback_parameters); 525 | } 526 | 527 | /** 528 | * Callbacks, if called in an array, should receive a "last" boolean 529 | * when they're in the last iteration of triggering - the last row in a result 530 | * array, for instance - for clearing things up 531 | */ 532 | public function test_callbacks_in_iteration_have_last_variable() 533 | { 534 | // stub 535 | } 536 | 537 | /* -------------------------------------------------------------- 538 | * PROTECTED ATTRIBUTES 539 | * ------------------------------------------------------------ */ 540 | 541 | public function test_protected_attributes() 542 | { 543 | $this->model = new Protected_attributes_model(); 544 | 545 | $author = array( 546 | 'id' => 123, 547 | 'hash' => 'dlkadflsdasdsadsds', 548 | 'title' => 'A new post' 549 | ); 550 | $author_obj = (object)$author; 551 | 552 | $author = $this->model->protect_attributes($author); 553 | $author_obj = $this->model->protect_attributes($author_obj); 554 | 555 | $this->assertFalse(isset($author['id'])); 556 | $this->assertFalse(isset($author['hash'])); 557 | $this->assertFalse(isset($author_obj->id)); 558 | $this->assertFalse(isset($author_obj->hash)); 559 | } 560 | 561 | /* -------------------------------------------------------------- 562 | * RELATIONSHIPS 563 | * ------------------------------------------------------------ */ 564 | 565 | public function test_belongs_to() 566 | { 567 | $object = (object)array( 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43 ); 568 | $author_object = (object)array( 'id' => 43, 'name' => 'Jamie', 'age' => 20 ); 569 | $expected_object = (object)array( 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43, 'author' => $author_object ); 570 | 571 | $self =& $this; 572 | 573 | $this->model = new Belongs_to_model(); 574 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 575 | $this->model->load = $this->getMock('MY_Model_Mock_Loader'); 576 | 577 | $author_model = new Author_modelCI(); 578 | $author_model->_database = $this->getMockBuilder('MY_Model_Mock_DB')->setMockClassName('Other_Mock_MY_Model_Mock_DB')->getMock(); 579 | 580 | $author_model->_database->expects($this->once())->method('where')->will($this->returnValue($author_model->_database)); 581 | $author_model->_database->expects($this->once())->method('get')->will($this->returnValue($author_model->_database)); 582 | 583 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 584 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 585 | 586 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($object)); 587 | $author_model->_database->expects($this->once())->method('row')->will($this->returnValue($author_object)); 588 | 589 | $this->model->load->expects($this->once())->method('model')->with('author_model', 'author_model') 590 | ->will($this->returnCallback(function() use ($self, $author_model){ 591 | $self->model->author_model = $author_model; 592 | })); 593 | 594 | $this->assertEquals($expected_object, $this->model->with('author')->get(1)); 595 | } 596 | 597 | public function test_has_many() 598 | { 599 | $object = (object)array( 'id' => 1, 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43 ); 600 | 601 | $comment_object = (object)array( 'id' => 1, 'comment' => 'A comment' ); 602 | $comment_object_2 = (object)array( 'id' => 2, 'comment' => 'Another comment' ); 603 | 604 | $expected_object = (object)array( 'id' => 1, 'title' => 'A Post', 'created_at' => time(), 'author_id' => 43, 605 | 'comments' => array( $comment_object, $comment_object_2 ) ); 606 | 607 | $self =& $this; 608 | 609 | $this->model = new Belongs_to_model(); 610 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 611 | $this->model->load = $this->getMock('MY_Model_Mock_Loader'); 612 | 613 | $comment_model = new Author_modelCI(); 614 | $comment_model->_database = $this->getMockBuilder('MY_Model_Mock_DB')->setMockClassName('Another_Mock_MY_Model_Mock_DB')->getMock(); 615 | 616 | $comment_model->_database->expects($this->once())->method('where')->with('comment_id', 1)->will($this->returnValue($comment_model->_database)); 617 | $comment_model->_database->expects($this->once())->method('get')->will($this->returnValue($comment_model->_database)); 618 | 619 | $this->model->_database->expects($this->once())->method('where')->will($this->returnValue($this->model->_database)); 620 | $this->model->_database->expects($this->once())->method('get')->will($this->returnValue($this->model->_database)); 621 | 622 | $this->model->_database->expects($this->once())->method('row')->will($this->returnValue($object)); 623 | $comment_model->_database->expects($this->once())->method('result')->will($this->returnValue(array( $comment_object, $comment_object_2 ))); 624 | 625 | $this->model->load->expects($this->once())->method('model')->with('comment_model', 'comments_model') 626 | ->will($this->returnCallback(function() use ($self, $comment_model){ 627 | $self->model->comments_model = $comment_model; 628 | })); 629 | 630 | $this->assertEquals($expected_object, $this->model->with('comments')->get(1)); 631 | } 632 | 633 | public function test_relate_works_with_objects_and_arrays() 634 | { 635 | $data = array( 'name' => 'Jamie', 'author_id' => 1 ); 636 | $author = 'related object'; 637 | 638 | $this->model = new Belongs_to_model(); 639 | $this->model->author_model = m::mock(new Author_modelCI()); 640 | $this->model->author_model->shouldReceive('get') 641 | ->andReturn($author); 642 | 643 | $obj = $this->model->with('author')->relate((object)$data); 644 | $arr = $this->model->with('author')->relate($data); 645 | 646 | $this->assertInternalType('object', $obj); 647 | $this->assertInternalType('array', $arr); 648 | $this->assertTrue(isset($obj->author)); 649 | $this->assertTrue(isset($arr['author'])); 650 | $this->assertEquals($author, $obj->author); 651 | $this->assertEquals($author, $arr['author']); 652 | } 653 | 654 | /* -------------------------------------------------------------- 655 | * VALIDATION 656 | * ------------------------------------------------------------ */ 657 | 658 | public function test_validate_correctly_returns_the_data_on_success_and_FALSE_on_failure() 659 | { 660 | $this->model = $this->_validatable_model(); 661 | $data = array( 'name' => 'Jamie', 'sexyness' => 'loads' ); 662 | 663 | $this->assertEquals($this->model->validate($data), $data); 664 | 665 | $this->model = $this->_validatable_model(FALSE); 666 | $this->assertEquals($this->model->validate($data), FALSE); 667 | } 668 | 669 | public function test_skip_validation() 670 | { 671 | $data = array( 'name' => 'Jamie', 'sexyness' => 'loads' ); 672 | $this->model = new Validated_model(); 673 | $this->model->form_validation = m::mock('form_validation_class'); 674 | $this->model->form_validation->shouldReceive('run')->never(); 675 | 676 | $this->model->skip_validation(); 677 | $this->assertEquals($this->model->validate($data), $data); 678 | } 679 | 680 | public function test_skip_validation_settings() 681 | { 682 | $ret = $this->model->skip_validation(); 683 | 684 | $this->assertEquals($ret, $this->model); 685 | $this->assertAttributeEquals(TRUE, 'skip_validation', $this->model); 686 | 687 | $ret = $this->model->enable_validation(); 688 | $this->assertEquals($ret, $this->model); 689 | 690 | $this->assertAttributeEquals(FALSE, 'skip_validation', $this->model); 691 | 692 | } 693 | 694 | protected function _validatable_model($validate_pass_or_fail = TRUE) 695 | { 696 | $model = new Validated_model(); 697 | $model->form_validation = m::mock('form_validation_class'); 698 | $model->form_validation->shouldIgnoreMissing(); 699 | $model->form_validation->shouldReceive('run') 700 | ->andReturn($validate_pass_or_fail); 701 | 702 | return $model; 703 | } 704 | 705 | /* -------------------------------------------------------------- 706 | * SOFT DELETE 707 | * ------------------------------------------------------------ */ 708 | 709 | public function test_soft_delete() 710 | { 711 | $this->model = new Soft_delete_model(); 712 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 713 | 714 | $this->model->_database->expects($this->once()) 715 | ->method('where') 716 | ->with($this->equalTo('id'), $this->equalTo(2)) 717 | ->will($this->returnValue($this->model->_database)); 718 | $this->model->_database->expects($this->once()) 719 | ->method('set') 720 | ->with($this->equalTo('deleted_at'), $this->equalTo('NOW()'), $this->equalTo(FALSE)) 721 | ->will($this->returnValue($this->model->_database)); 722 | 723 | $this->model->_database->expects($this->once()) 724 | ->method('update') 725 | ->with($this->equalTo('records')) 726 | ->will($this->returnValue(TRUE)); 727 | 728 | $this->assertEquals($this->model->delete(2), TRUE); 729 | } 730 | 731 | public function test_soft_delete_custom_key() 732 | { 733 | $this->model = new Soft_delete_model('record_deleted_at'); 734 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 735 | 736 | $this->model->_database->expects($this->once()) 737 | ->method('where') 738 | ->with($this->equalTo('id'), $this->equalTo(2)) 739 | ->will($this->returnValue($this->model->_database)); 740 | $this->model->_database->expects($this->once()) 741 | ->method('set') 742 | ->with($this->equalTo('record_deleted_at'), $this->equalTo('NOW()'), $this->equalTo(FALSE)) 743 | ->will($this->returnValue($this->model->_database)); 744 | $this->model->_database->expects($this->once()) 745 | ->method('update') 746 | ->with($this->equalTo('records')) 747 | ->will($this->returnValue(TRUE)); 748 | 749 | $this->assertEquals($this->model->delete(2), TRUE); 750 | } 751 | 752 | public function test_soft_delete_by() 753 | { 754 | $this->model = new Soft_delete_model(); 755 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 756 | 757 | $this->model->_database->expects($this->once()) 758 | ->method('where') 759 | ->with($this->equalTo('key'), $this->equalTo('value')) 760 | ->will($this->returnValue($this->model->_database)); 761 | $this->model->_database->expects($this->once()) 762 | ->method('set') 763 | ->with($this->equalTo('deleted_at'), $this->equalTo('NOW()'), $this->equalTo(FALSE)) 764 | ->will($this->returnValue($this->model->_database)); 765 | $this->model->_database->expects($this->once()) 766 | ->method('update') 767 | ->with($this->equalTo('records')) 768 | ->will($this->returnValue(TRUE)); 769 | 770 | $this->assertEquals($this->model->delete_by('key', 'value'), TRUE); 771 | } 772 | 773 | public function test_soft_delete_many() 774 | { 775 | $this->model = new Soft_delete_model(); 776 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 777 | 778 | $this->model->_database->expects($this->once()) 779 | ->method('where_in') 780 | ->with($this->equalTo('id'), $this->equalTo(array(2, 4, 6))) 781 | ->will($this->returnValue($this->model->_database)); 782 | $this->model->_database->expects($this->once()) 783 | ->method('set') 784 | ->with($this->equalTo('deleted_at'), $this->equalTo('NOW()'), $this->equalTo(FALSE)) 785 | ->will($this->returnValue($this->model->_database)); 786 | $this->model->_database->expects($this->once()) 787 | ->method('update') 788 | ->with($this->equalTo('records')) 789 | ->will($this->returnValue(TRUE)); 790 | 791 | $this->assertEquals($this->model->delete_many(array(2, 4, 6)), TRUE); 792 | } 793 | 794 | public function test_soft_delete_get() 795 | { 796 | $this->model = new Soft_delete_model(); 797 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 798 | 799 | $this->model->_database->expects($this->at(0)) 800 | ->method('where') 801 | ->with($this->equalTo("(deleted_at > NOW() OR deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00')")) 802 | ->will($this->returnValue($this->model->_database)); 803 | $this->model->_database->expects($this->at(1)) 804 | ->method('where') 805 | ->with($this->equalTo('id'), $this->equalTo(2)) 806 | ->will($this->returnValue($this->model->_database)); 807 | $this->_expect_get(); 808 | $this->model->_database->expects($this->once()) 809 | ->method('row') 810 | ->will($this->returnValue('fake_record_here')); 811 | 812 | $this->assertEquals($this->model->get(2), 'fake_record_here'); 813 | } 814 | 815 | public function test_soft_delete_dropdown() 816 | { 817 | $this->model = new Soft_delete_model(); 818 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 819 | 820 | $fake_row_1 = array( 'id' => 1, 'name' => 'Jamie' ); 821 | $fake_row_2 = array( 'id' => 2, 'name' => 'Laura' ); 822 | $fake_results = array( (object)$fake_row_1, (object)$fake_row_2 ); 823 | 824 | $this->model->_database->expects($this->at(0)) 825 | ->method('where') 826 | ->with($this->equalTo("(deleted_at > NOW() OR deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00')")) 827 | ->will($this->returnValue($this->model->_database)); 828 | 829 | $this->model->_database->expects($this->once()) 830 | ->method('select') 831 | ->with($this->equalTo(array('id', 'name'))) 832 | ->will($this->returnValue($this->model->_database)); 833 | $this->_expect_get(); 834 | $this->model->_database->expects($this->any()) 835 | ->method('result') 836 | ->will($this->returnValue($fake_results)); 837 | 838 | $this->model->dropdown('name'); 839 | } 840 | 841 | public function test_with_deleted() 842 | { 843 | $this->model = new Soft_delete_model(); 844 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 845 | 846 | $this->model->_database->expects($this->exactly(1)) 847 | ->method('where') 848 | ->with($this->equalTo('id'), $this->equalTo(2)) 849 | ->will($this->returnValue($this->model->_database)); 850 | $this->_expect_get(); 851 | $this->model->_database->expects($this->once()) 852 | ->method('row') 853 | ->will($this->returnValue('fake_record_here')); 854 | 855 | $this->assertEquals($this->model->with_deleted()->get(2), 'fake_record_here'); 856 | } 857 | 858 | public function test_only_deleted() 859 | { 860 | $this->model = new Soft_delete_model(); 861 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 862 | 863 | $this->model->_database->expects($this->once()) 864 | ->method('where') 865 | ->with($this->equalTo("`deleted_at` <= NOW()")) 866 | ->will($this->returnValue($this->model->_database)); 867 | $this->_expect_get(); 868 | $this->model->_database->expects($this->once()) 869 | ->method('result') 870 | ->will($this->returnValue(array('fake_record_here'))); 871 | 872 | $this->assertEquals($this->model->only_deleted()->get_all(), array('fake_record_here')); 873 | } 874 | 875 | public function test_soft_delete_future_delete() 876 | { 877 | $whenToDelete = new \DateTime(); 878 | $whenToDelete = $whenToDelete->modify('+1 day')->format('Y-m-d H:i:s'); 879 | 880 | $this->model = new Soft_delete_model(); 881 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 882 | 883 | $this->model->_database->expects($this->once()) 884 | ->method('where') 885 | ->with($this->equalTo('id'), $this->equalTo(2)) 886 | ->will($this->returnValue($this->model->_database)); 887 | $this->model->_database->expects($this->once()) 888 | ->method('set') 889 | ->with($this->equalTo('deleted_at'), $this->equalTo($whenToDelete), $this->equalTo(TRUE)) 890 | ->will($this->returnValue($this->model->_database)); 891 | 892 | $this->model->_database->expects($this->once()) 893 | ->method('update') 894 | ->with($this->equalTo('records')) 895 | ->will($this->returnValue(TRUE)); 896 | 897 | $this->assertEquals($this->model->delete_at(2, $whenToDelete), TRUE); 898 | } 899 | 900 | /* -------------------------------------------------------------- 901 | * CALLBACKS 902 | * ------------------------------------------------------------ */ 903 | 904 | public function test_serialize() 905 | { 906 | $this->model = new Serialised_data_model(); 907 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 908 | 909 | $data = array( 'name' => 'Jamie', 'awesomeness_level' => 1000000 ); 910 | 911 | $this->model->_database->expects($this->exactly(1)) 912 | ->method('insert') 913 | ->with($this->equalTo('records'), $this->equalTo(array( 'data' => serialize($data) ))); 914 | 915 | $this->model->insert(array( 'data' => $data )); 916 | } 917 | 918 | public function test_timestamps() 919 | { 920 | $this->model = new Timestamp_model(); 921 | 922 | $data = array( 'name' => 'Jamie' ); 923 | $obj = (object)array( 'name' => 'Jamie' ); 924 | 925 | $data = $this->model->created_at($data); 926 | $obj = $this->model->created_at($obj); 927 | $data = $this->model->updated_at($data); 928 | $obj = $this->model->updated_at($obj); 929 | 930 | $this->assertTrue(isset($data['created_at'])); 931 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $data['created_at']); 932 | $this->assertTrue(isset($obj->created_at)); 933 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->created_at); 934 | $this->assertTrue(isset($data['updated_at'])); 935 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $data['updated_at']); 936 | $this->assertTrue(isset($obj->updated_at)); 937 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->updated_at); 938 | } 939 | 940 | public function test_blamable() 941 | { 942 | $this->model = new Timestamp_model(); 943 | $this->model->_database = $this->getMock('MY_Model_Mock_DB'); 944 | 945 | $obj = (object)array( 'name' => 'Jamie' ); 946 | 947 | $updateObject = (object)array('id'=>1, 'name' => 'Jamie' ); 948 | 949 | $this->model->_database->expects($this->once()) 950 | ->method('insert') 951 | ->with($this->equalTo('records'), $this->equalTo($obj)); 952 | $this->model->_database->expects($this->any()) 953 | ->method('insert_id') 954 | ->will($this->returnValue(2)); 955 | 956 | $this->model->_database->expects($this->once()) 957 | ->method('where') 958 | ->with($this->equalTo('id'), $this->equalTo(1)) 959 | ->will($this->returnValue($this->model->_database)); 960 | $this->model->_database->expects($this->once()) 961 | ->method('set') 962 | ->with($this->equalTo($updateObject)) 963 | ->will($this->returnValue($this->model->_database)); 964 | $this->model->_database->expects($this->once()) 965 | ->method('update') 966 | ->with($this->equalTo('records')) 967 | ->will($this->returnValue(TRUE)); 968 | 969 | $this->assertEquals($this->model->insert($obj), 2); 970 | $this->assertEquals($this->model->update(1, $updateObject), true); 971 | 972 | 973 | $this->assertTrue(isset($obj->created_at)); 974 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->created_at); 975 | 976 | $this->assertTrue(isset($obj->updated_at)); 977 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $obj->updated_at); 978 | 979 | $this->assertTrue(isset($updateObject->updated_at)); 980 | $this->assertRegExp("/[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/", $updateObject->updated_at); 981 | } 982 | 983 | /* -------------------------------------------------------------- 984 | * UTILITY METHODS 985 | * ------------------------------------------------------------ */ 986 | 987 | public function test_dropdown() 988 | { 989 | $fake_row_1 = array( 'id' => 1, 'name' => 'Jamie' ); 990 | $fake_row_2 = array( 'id' => 2, 'name' => 'Laura' ); 991 | 992 | $fake_results = array( (object)$fake_row_1, (object)$fake_row_2 ); 993 | 994 | $this->model->_database->expects($this->once()) 995 | ->method('select') 996 | ->with($this->equalTo(array('id', 'name'))) 997 | ->will($this->returnValue($this->model->_database)); 998 | $this->_expect_get(); 999 | $this->model->_database->expects($this->any()) 1000 | ->method('result') 1001 | ->will($this->returnValue($fake_results)); 1002 | 1003 | $this->assertEquals($this->model->dropdown('name'), array( 1 => 'Jamie', 2 => 'Laura' )); 1004 | } 1005 | 1006 | public function test_count_by() 1007 | { 1008 | $this->model->_database->expects($this->once()) 1009 | ->method('where') 1010 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 1011 | ->will($this->returnValue($this->model->_database)); 1012 | $this->model->_database->expects($this->once()) 1013 | ->method('count_all_results') 1014 | ->will($this->returnValue(5)); 1015 | 1016 | $this->assertEquals($this->model->count_by('some_column', 'some_value'), 5); 1017 | } 1018 | 1019 | public function test_count_all() 1020 | { 1021 | $this->model->_database->expects($this->once()) 1022 | ->method('count_all') 1023 | ->with($this->equalTo('records')) 1024 | ->will($this->returnValue(200)); 1025 | $this->assertEquals($this->model->count_all(), 200); 1026 | } 1027 | 1028 | public function test_get_next_id() 1029 | { 1030 | $this->model->_database->database = 'some_database_name'; 1031 | 1032 | $this->model->_database->expects($this->once()) 1033 | ->method('select') 1034 | ->with($this->equalTo('AUTO_INCREMENT')) 1035 | ->will($this->returnValue($this->model->_database)); 1036 | $this->model->_database->expects($this->once()) 1037 | ->method('from') 1038 | ->with($this->equalTo('information_schema.TABLES')) 1039 | ->will($this->returnValue($this->model->_database)); 1040 | $this->model->_database->expects($this->any()) 1041 | ->method('where') 1042 | ->will($this->returnValue($this->model->_database)); 1043 | $this->model->_database->expects($this->once()) 1044 | ->method('get') 1045 | ->will($this->returnValue($this->model->_database)); 1046 | $this->model->_database->expects($this->once()) 1047 | ->method('row') 1048 | ->will($this->returnValue((object)array( 'AUTO_INCREMENT' => 250 ))); 1049 | 1050 | $this->assertEquals($this->model->get_next_id(), 250); 1051 | } 1052 | 1053 | public function test_as_array() 1054 | { 1055 | $this->model->_database->expects($this->once()) 1056 | ->method('where') 1057 | ->with($this->equalTo('id'), $this->equalTo(2)) 1058 | ->will($this->returnValue($this->model->_database)); 1059 | $this->_expect_get(); 1060 | $this->model->_database->expects($this->once()) 1061 | ->method('row_array') 1062 | ->will($this->returnValue('fake_record_here')); 1063 | 1064 | $this->assertEquals($this->model->as_array()->get(2), 'fake_record_here'); 1065 | } 1066 | 1067 | /* -------------------------------------------------------------- 1068 | * QUERY BUILDER DIRECT ACCESS METHODS 1069 | * ------------------------------------------------------------ */ 1070 | 1071 | public function test_order_by_regular() 1072 | { 1073 | $this->model->_database->expects($this->once()) 1074 | ->method('order_by') 1075 | ->with($this->equalTo('some_column'), $this->equalTo('DESC')); 1076 | 1077 | $this->assertEquals($this->model->order_by('some_column', 'DESC'), $this->model); 1078 | } 1079 | 1080 | public function test_order_by_array() 1081 | { 1082 | $this->model->_database->expects($this->once()) 1083 | ->method('order_by') 1084 | ->with($this->equalTo('some_column'), $this->equalTo('ASC')); 1085 | 1086 | $this->assertEquals($this->model->order_by(array('some_column' => 'ASC')), $this->model); 1087 | } 1088 | 1089 | public function test_limit() 1090 | { 1091 | $this->model->_database->expects($this->once()) 1092 | ->method('limit') 1093 | ->with($this->equalTo(10), $this->equalTo(5)); 1094 | 1095 | $this->assertEquals($this->model->limit(10, 5), $this->model); 1096 | } 1097 | 1098 | public function test_truncate() 1099 | { 1100 | $this->model->_database->expects($this->once()) 1101 | ->method('truncate') 1102 | ->with($this->equalTo('records')); 1103 | 1104 | $this->model->truncate(); 1105 | } 1106 | 1107 | /* -------------------------------------------------------------- 1108 | * TEST UTILITIES 1109 | * ------------------------------------------------------------ */ 1110 | 1111 | protected function _expect_get() 1112 | { 1113 | $this->model->_database->expects($this->once()) 1114 | ->method('get') 1115 | ->with($this->equalTo('records')) 1116 | ->will($this->returnValue($this->model->_database)); 1117 | } 1118 | 1119 | protected function _list_fields() 1120 | { 1121 | $this->model->_database->expects($this->once()) 1122 | ->method('list_fields') 1123 | ->with($this->equalTo($this->model->table())) 1124 | ->will($this->returnValue($this->model->get_fields())); 1125 | } 1126 | 1127 | private function _mock_get_by_call() 1128 | { 1129 | $this->model->_database->expects($this->once()) 1130 | ->method('where') 1131 | ->with($this->equalTo('some_column'), $this->equalTo('some_value')) 1132 | ->will($this->returnValue($this->model->_database)); 1133 | $this->_expect_get(); 1134 | $this->model->_database->expects($this->once()) 1135 | ->method('row') 1136 | ->will($this->returnValue('fake_record_here')); 1137 | } 1138 | 1139 | /* -------------------------------------------------------------- 1140 | * CUSTOM ASSERTIONS 1141 | * ------------------------------------------------------------ */ 1142 | 1143 | public function assertCallbackIsCalled($method, $params = null) 1144 | { 1145 | try 1146 | { 1147 | $method(); 1148 | $this->fail('Callback wasn\'t called'); 1149 | } 1150 | catch (Callback_Test_Exception $e) 1151 | { 1152 | if (!is_null($params)) 1153 | { 1154 | $this->assertEquals($e->passed_object, $params); 1155 | } 1156 | } 1157 | } 1158 | } -------------------------------------------------------------------------------- /src/CI_Base_Model.php: -------------------------------------------------------------------------------- 1 | 7 | * @author Md Emran Hasan 8 | * @author Roni Kumar Saha 9 | * @version 2.1 10 | * 11 | * @link http://github.com/jamierumbelow/codeigniter-base-model 12 | * @link https://github.com/phpfour/MY_Model 13 | * @link https://github.com/ronisaha/ci-base-model 14 | * 15 | */ 16 | 17 | class CI_Base_Model extends CI_Model 18 | { 19 | 20 | /* -------------------------------------------------------------- 21 | * VARIABLES 22 | * ------------------------------------------------------------ */ 23 | /** 24 | * This model's default database table. Automatically 25 | * guessed by pluralising the model name. 26 | */ 27 | protected $_table; 28 | 29 | /** 30 | * The database connection object. Will be set to the default 31 | * connection. This allows individual models to use different DBs 32 | * without overwriting CI's global $this->db connection. 33 | */ 34 | protected $_database; 35 | 36 | protected $_database_group = null; 37 | protected static $_connection_cache = array(); 38 | 39 | /** 40 | * This model's default primary key or unique identifier. 41 | * Used by the get(), update() and delete() functions. 42 | */ 43 | protected $primary_key = NULL; 44 | 45 | /** 46 | * Support for soft deletes and this model's 'deleted' key 47 | */ 48 | protected $deleted_at_key = 'deleted_at'; 49 | protected $deleted_by_key = 'deleted_by'; 50 | protected $_temporary_with_deleted = FALSE; 51 | protected $_temporary_only_deleted = FALSE; 52 | private $soft_delete = NULL; 53 | 54 | /** 55 | * Support for Timestampable 56 | */ 57 | protected $timestampable; 58 | protected $created_at_key = 'created_at'; 59 | protected $updated_at_key = 'updated_at'; 60 | 61 | /** 62 | * Support for Blamable 63 | */ 64 | protected $blamable; 65 | protected $created_by_key = 'created_by'; 66 | protected $updated_by_key = 'updated_by'; 67 | 68 | 69 | /** 70 | * The various default callbacks available to the model. Each are 71 | * simple lists of method names (methods will be run on $this). 72 | */ 73 | protected $before_create = array(); 74 | protected $after_create = array(); 75 | protected $before_update = array(); 76 | protected $after_update = array(); 77 | protected $before_get = array(); 78 | protected $after_get = array(); 79 | protected $before_delete = array(); 80 | protected $after_delete = array(); 81 | 82 | protected $event_listeners = array( 83 | 'before_create' => array(), 84 | 'after_create' => array(), 85 | 'before_update' => array(), 86 | 'after_update' => array(), 87 | 'before_get' => array(), 88 | 'after_get' => array(), 89 | 'before_delete' => array(), 90 | 'after_delete' => array(), 91 | ); 92 | 93 | protected $callback_parameters = array(); 94 | 95 | /** 96 | * Protected, non-modifiable attributes 97 | */ 98 | protected $protected_attributes = array(); 99 | 100 | /** 101 | * Relationship arrays. Use flat strings for defaults or string 102 | * => array to customise the class name and primary key 103 | */ 104 | protected $belongs_to = array(); 105 | protected $has_many = array(); 106 | 107 | protected $_with = array(); 108 | 109 | /** 110 | * An array of validation rules. This needs to be the same format 111 | * as validation rules passed to the Form_validation library. 112 | */ 113 | protected $validate = array(); 114 | 115 | /** 116 | * Optionally skip the validation. Used in conjunction with 117 | * skip_validation() to skip data validation for any future calls. 118 | */ 119 | protected $skip_validation = FALSE; 120 | 121 | /** 122 | * By default we return our results as objects. If we need to override 123 | * this, we can, or, we could use the `as_array()` and `as_object()` scopes. 124 | */ 125 | protected $return_type = 'object'; 126 | protected $_temporary_return_type = NULL; 127 | 128 | /** 129 | * @var array list of fields 130 | */ 131 | protected $_fields = array(); 132 | 133 | /** 134 | * @var int returned number of rows of a query 135 | */ 136 | protected $num_rows = NULL; 137 | 138 | private $_base_model_instance = NULL; 139 | 140 | /* -------------------------------------------------------------- 141 | * GENERIC METHODS 142 | * ------------------------------------------------------------ */ 143 | 144 | /** 145 | * Initialise the model, tie into the CodeIgniter superobject 146 | */ 147 | public function __construct() 148 | { 149 | parent::__construct(); 150 | 151 | $this->load->helper('inflector'); 152 | 153 | $this->_initialize_event_listeners(); 154 | $this->_initialize_schema(); 155 | 156 | $this->_temporary_return_type = $this->return_type; 157 | } 158 | 159 | 160 | /** 161 | * Initialize the schema for special use cases 162 | * and try our best to guess the table name, primary_key 163 | * and the blamable, timestampable, softDeletable status 164 | */ 165 | protected function _initialize_schema() 166 | { 167 | $this->set_database($this->_database_group); 168 | 169 | $this->_fetch_table(); 170 | $this->_fetch_primary_key(); 171 | 172 | if($this->primary_key == null && $this->is_base_model_instance()) { 173 | return; 174 | } 175 | 176 | $this->_fields = $this->get_fields(); 177 | 178 | $this->_guess_is_soft_deletable(); 179 | $this->_guess_is_blamable(); 180 | $this->_guess_is_timestampable(); 181 | 182 | } 183 | 184 | /** 185 | * Initialize all default listeners 186 | */ 187 | protected function _initialize_event_listeners() 188 | { 189 | foreach($this->event_listeners as $event_listener => $e) 190 | { 191 | if(isset($this->$event_listener) && !empty($this->$event_listener)){ 192 | foreach($this->$event_listener as $event){ 193 | $this->subscribe($event_listener, $event); 194 | } 195 | } 196 | } 197 | 198 | $this->subscribe('before_update', 'protect_attributes', TRUE); 199 | } 200 | 201 | /* -------------------------------------------------------------- 202 | * CRUD INTERFACE 203 | * ------------------------------------------------------------ */ 204 | 205 | /** 206 | * Fetch a single record based on the primary key. Returns an object. 207 | */ 208 | public function get($primary_value) 209 | { 210 | return $this->get_by($this->primary_key, $primary_value); 211 | } 212 | 213 | /** 214 | * Fetch a single record based on an arbitrary WHERE call. Can be 215 | * any valid value to $this->_database->where(). 216 | */ 217 | public function get_by() 218 | { 219 | $where = func_get_args(); 220 | 221 | $this->apply_soft_delete_filter(); 222 | 223 | $this->_set_where($where); 224 | 225 | $this->trigger('before_get'); 226 | 227 | $this->limit(1); 228 | 229 | $result = $this->_database->get($this->_table); 230 | 231 | $this->num_rows = count((array)$result); 232 | 233 | $row = $result->{$this->_get_return_type_method()}(); 234 | $this->_temporary_return_type = $this->return_type; 235 | 236 | $row = $this->trigger('after_get', $row); 237 | 238 | $this->_with = array(); 239 | return $row; 240 | } 241 | 242 | /** 243 | * Fetch an array of records based on an array of primary values. 244 | */ 245 | public function get_many($values) 246 | { 247 | $this->apply_soft_delete_filter(); 248 | 249 | $this->_database->where_in($this->primary_key, $values); 250 | 251 | return $this->get_all(); 252 | } 253 | 254 | /** 255 | * Fetch an array of records based on an arbitrary WHERE call. 256 | */ 257 | public function get_many_by() 258 | { 259 | $where = func_get_args(); 260 | 261 | $this->apply_soft_delete_filter(); 262 | 263 | $this->_set_where($where); 264 | 265 | return $this->get_all(); 266 | } 267 | 268 | /** 269 | * Fetch all the records in the table. Can be used as a generic call 270 | * to $this->_database->get() with scoped methods. 271 | */ 272 | public function get_all() 273 | { 274 | $this->trigger('before_get'); 275 | 276 | $this->apply_soft_delete_filter(); 277 | 278 | $result = $this->_database->get($this->_table) 279 | ->{$this->_get_return_type_method(true)}(); 280 | $this->_temporary_return_type = $this->return_type; 281 | 282 | $this->num_rows = count($result); 283 | 284 | foreach ($result as $key => &$row) 285 | { 286 | $row = $this->trigger('after_get', $row, ($key == count($result) - 1)); 287 | } 288 | 289 | $this->_with = array(); 290 | return $result; 291 | } 292 | 293 | /** 294 | * @param $methodName 295 | * @param $args 296 | * 297 | * @return mixed 298 | */ 299 | public function __call($methodName, $args) 300 | { 301 | $watch = array('find_by', 'find_all_by', 'find_field_by', 'findBy', 'findAllBy', 'findFieldBy'); 302 | 303 | foreach ($watch as $found) { 304 | if ($methodName == $found) { 305 | break; 306 | } 307 | 308 | if (stristr($methodName, $found)) { 309 | $field = $this->underscore_from_camel_case(ltrim(str_replace($found, '', $methodName), '_')); 310 | $method = $this->underscore_from_camel_case($found); 311 | return $this->$method($field, $args); 312 | } 313 | } 314 | 315 | $method = self::underscore_from_camel_case($methodName); 316 | 317 | if (method_exists($this, $method)) { 318 | return call_user_func_array(array($this, $method), $args); 319 | } 320 | 321 | return $this->_handle_exception($methodName); 322 | 323 | } 324 | 325 | public static function underscore_from_camel_case($str) { 326 | $str[0] = strtolower($str[0]); 327 | $func = create_function('$c', 'return "_" . strtolower($c[1]);'); 328 | return preg_replace_callback('/([A-Z])/', $func, $str); 329 | } 330 | 331 | /** 332 | * Returns a property value based on its name. 333 | * Do not call this method. This is a PHP magic method that we override 334 | * to allow using the following syntax to read a property or obtain event handlers: 335 | *
 336 |      * $value=$model->propertyName; [will be called $this->get_property_name()]
 337 |      * $value=$model->property_name; [will be called $this->get_property_name()]
 338 |      * $value=$model->load; [will be called $controller->load]
 339 |      * 
340 | * 341 | * @param string $name the property name 342 | * 343 | * @return mixed the property value 344 | * @see __set 345 | */ 346 | public function __get($name) 347 | { 348 | $getter = 'get' . $name; 349 | $getter2 = 'get_' . $name; 350 | $getter3 = 'get_' . self::underscore_from_camel_case($name); 351 | if (method_exists($this, $getter)) 352 | return $this->$getter(); 353 | elseif (method_exists($this, $getter2)){ 354 | return $this->$getter2(); 355 | } 356 | elseif (method_exists($this, $getter3)){ 357 | return $this->$getter3(); 358 | } 359 | 360 | return parent::__get($name); 361 | } 362 | 363 | 364 | /** 365 | * @param $field 366 | * @param $value 367 | * @param string $fields 368 | * @param null $order 369 | * 370 | * @return bool 371 | */ 372 | public function find_by($field, $value, $fields = '*', $order = NULL) 373 | { 374 | $arg_list = array(); 375 | if (is_array($value)) { 376 | $arg_list = $value; 377 | $value = $arg_list[0]; 378 | } 379 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields; 380 | $order = isset($arg_list[2]) ? $arg_list[2] : $order; 381 | 382 | $where = array($field => $value); 383 | return $this->find($where, $fields, $order); 384 | } 385 | 386 | /** 387 | * @param $field 388 | * @param $value 389 | * @param string $fields 390 | * @param null $order 391 | * @param int $start 392 | * @param null $limit 393 | * 394 | * @return mixed 395 | */ 396 | public function find_all_by($field, $value, $fields = '*', $order = NULL, $start = 0, $limit = NULL) 397 | { 398 | $arg_list = array(); 399 | if (is_array($value)) { 400 | $arg_list = $value; 401 | $value = $arg_list[0]; 402 | } 403 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields; 404 | $order = isset($arg_list[2]) ? $arg_list[2] : $order; 405 | $start = isset($arg_list[3]) ? $arg_list[3] : $start; 406 | $limit = isset($arg_list[4]) ? $arg_list[4] : $limit; 407 | 408 | $where = array($field => $value); 409 | return $this->find_all($where, $fields, $order, $start, $limit); 410 | } 411 | 412 | /** 413 | * 414 | * @param $field 415 | * @param $value 416 | * @param string $fields 417 | * @param null $order 418 | * 419 | * @return mixed 420 | */ 421 | public function find_field_by($field, $value, $fields = '*', $order = NULL) 422 | { 423 | $arg_list = array(); 424 | 425 | if (is_array($value)) { 426 | $arg_list = $value; 427 | $value = $arg_list[0]; 428 | } 429 | 430 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields; 431 | $order = isset($arg_list[2]) ? $arg_list[2] : $order; 432 | $where = array($field => $value); 433 | 434 | return $this->field($where, $fields, $fields, $order); 435 | } 436 | 437 | /** 438 | * Insert a new row into the table. $data should be an associative array 439 | * of data to be inserted. Returns newly created ID. 440 | * @param $data 441 | * @return bool 442 | */ 443 | public function insert($data) 444 | { 445 | if (false !== $data = $this->_do_pre_create($data)) { 446 | $this->_database->insert($this->_table, $data); 447 | $insert_id = $this->_database->insert_id(); 448 | 449 | $this->trigger('after_create', $insert_id); 450 | 451 | return $insert_id; 452 | } 453 | 454 | return false; 455 | } 456 | 457 | /** 458 | * Insert multiple rows into the table. Returns an array of multiple IDs. 459 | * @param $data 460 | * @param bool $insert_individual 461 | * @return array 462 | */ 463 | public function insert_many($data, $insert_individual = false) 464 | { 465 | if($insert_individual){ 466 | return $this->_insert_individual($data); 467 | } 468 | 469 | return $this->_insert_batch($data); 470 | } 471 | 472 | private function _insert_individual($data) 473 | { 474 | $ids = array(); 475 | 476 | foreach ($data as $key => $row) 477 | { 478 | if(FALSE !== $row = $this->_do_pre_create($row)) { 479 | $ids[] = $this->insert($row); 480 | } 481 | } 482 | 483 | return $ids; 484 | } 485 | 486 | private function _insert_batch($data) 487 | { 488 | $_data = array(); 489 | foreach ($data as $key => $row) 490 | { 491 | if(FALSE !== $row = $this->_do_pre_create($row)){ 492 | $_data[$key] = $row; 493 | } 494 | } 495 | 496 | return $this->_database->insert_batch($this->_table, $_data); 497 | } 498 | 499 | /** 500 | * Updated a record based on the primary value. 501 | * @param $primary_value 502 | * @param $data 503 | * @return bool 504 | */ 505 | public function update($primary_value, $data) 506 | { 507 | $data = $this->_do_pre_update($data); 508 | 509 | if ($data !== FALSE) 510 | { 511 | $result = $this->_database->where($this->primary_key, $primary_value) 512 | ->set($data) 513 | ->update($this->_table); 514 | 515 | $this->trigger('after_update', array($data, $result)); 516 | 517 | return $result; 518 | } 519 | else 520 | { 521 | return FALSE; 522 | } 523 | } 524 | 525 | /** 526 | * Update many records, based on an array of primary values. 527 | * @param $primary_values 528 | * @param $data 529 | * @return bool 530 | */ 531 | public function update_many($primary_values, $data) 532 | { 533 | $data = $this->_do_pre_update($data); 534 | 535 | if ($data !== FALSE) 536 | { 537 | $result = $this->_database->where_in($this->primary_key, $primary_values) 538 | ->set($data) 539 | ->update($this->_table); 540 | 541 | $this->trigger('after_update', array($data, $result)); 542 | 543 | return $result; 544 | } 545 | 546 | return FALSE; 547 | } 548 | 549 | /** 550 | * Updated a record based on an arbitrary WHERE clause. 551 | */ 552 | public function update_by() 553 | { 554 | $args = func_get_args(); 555 | $data = array_pop($args); 556 | 557 | $data = $this->_do_pre_update($data); 558 | 559 | if ($data !== FALSE) 560 | { 561 | $this->_set_where($args); 562 | return $this->_update($data); 563 | } 564 | 565 | return FALSE; 566 | } 567 | 568 | /** 569 | * Update all records 570 | * @param $data 571 | * @return mixed 572 | */ 573 | public function update_all($data) 574 | { 575 | $data = $this->_do_pre_update($data); 576 | return $this->_update($data); 577 | } 578 | 579 | /** 580 | * Update all records 581 | * @param $data 582 | * @param $where_key 583 | * @return 584 | */ 585 | public function update_batch($data, $where_key) 586 | { 587 | $_data = array(); 588 | 589 | foreach ($data as $key => $row) { 590 | 591 | if (false !== $row = $this->_do_pre_update($row)) { 592 | $_data[$key] = $row; 593 | } 594 | } 595 | 596 | return $this->_database->update_batch($this->_table, $_data, $where_key); 597 | } 598 | 599 | 600 | 601 | /** 602 | * @param null $data 603 | * @param null $update 604 | * 605 | * @return bool 606 | */ 607 | public function on_duplicate_update($data = NULL, $update = NULL) 608 | { 609 | if (is_null($data)) { 610 | return FALSE; 611 | } 612 | 613 | if (is_null($update)) { 614 | $update = $data; 615 | } 616 | 617 | $sql = $this->_duplicate_insert_sql($data, $update); 618 | 619 | return $this->execute_query($sql); 620 | } 621 | 622 | /** 623 | * @param $values 624 | * @param null $update 625 | * 626 | * @return string 627 | */ 628 | protected function _duplicate_insert_sql($values, $update) 629 | { 630 | $updateStr = array(); 631 | $keyStr = array(); 632 | $valStr = array(); 633 | 634 | $values = $this->trigger('before_create', $values); 635 | $update = $this->trigger('before_update', $update); 636 | 637 | foreach ($values as $key => $val) { 638 | $keyStr[] = $key; 639 | $valStr[] = $this->_database->escape($val); 640 | } 641 | 642 | foreach ($update as $key => $val) { 643 | $updateStr[] = $key . " = '{$val}'"; 644 | } 645 | 646 | $sql = "INSERT INTO `" . $this->_database->dbprefix($this->_table) . "` (" . implode(', ', $keyStr) . ") "; 647 | $sql .= "VALUES (" . implode(', ', $valStr) . ") "; 648 | $sql .= "ON DUPLICATE KEY UPDATE " . implode(", ", $updateStr); 649 | 650 | return $sql; 651 | } 652 | 653 | /** 654 | * @param $condition 655 | * @param string $time 656 | * @return bool|mixed|void 657 | */ 658 | protected function _delete($condition, $time = 'NOW()') 659 | { 660 | $this->trigger('before_delete', $condition); 661 | 662 | if ($this->soft_delete) { 663 | $escape = $time != 'NOW()'; 664 | 665 | if(!is_string($time)){ 666 | $time = $this->get_mysql_time($time); 667 | } 668 | 669 | $this->_database->set($this->deleted_at_key, $time, $escape); 670 | $result = $this->_database->update($this->_table); 671 | } else { 672 | $result = $this->_database->delete($this->_table); 673 | } 674 | 675 | return $this->trigger('after_delete', $result); 676 | } 677 | 678 | protected function get_mysql_time($time) 679 | { 680 | if($time instanceof DateTime){ 681 | return $time->format('Y-m-d H:i:s'); 682 | } 683 | 684 | return date('Y-m-d H:i:s', $time); 685 | } 686 | 687 | protected function prevent_if_not_soft_deletable() 688 | { 689 | if (!$this->soft_delete || !isset($this->deleted_at_key) || empty($this->deleted_at_key)) { 690 | throw new Exception('This model does not setup properly to use soft delete'); 691 | } 692 | } 693 | 694 | 695 | /** 696 | * Delete a row from the table by the primary value 697 | */ 698 | public function delete($id, $time = 'NOW()') 699 | { 700 | $this->_database->where($this->primary_key, $id); 701 | 702 | return $this->_delete($id, $time); 703 | } 704 | 705 | 706 | /** 707 | * Alias for delete 708 | * 709 | * @param $id 710 | * @param $time 711 | * @return bool|mixed|void 712 | */ 713 | public function delete_at($id, $time) 714 | { 715 | $this->prevent_if_not_soft_deletable(); 716 | 717 | return $this->delete($id, $time); 718 | } 719 | 720 | /** 721 | * Alias for delete_by 722 | * 723 | * @param $condition 724 | * @param $time 725 | * @return bool|mixed|void 726 | */ 727 | public function delete_by_at($condition, $time) 728 | { 729 | $this->prevent_if_not_soft_deletable(); 730 | $this->_set_where($condition); 731 | 732 | return $this->_delete($condition, $time); 733 | } 734 | 735 | /** 736 | * Alias for delete_many 737 | * 738 | * @param $primary_values 739 | * @param $time 740 | * @return bool|mixed|void 741 | */ 742 | public function delete_many_at($primary_values, $time) 743 | { 744 | $this->prevent_if_not_soft_deletable(); 745 | 746 | return $this->delete_many($primary_values, $time); 747 | } 748 | 749 | /** 750 | * Delete a row from the database table by an arbitrary WHERE clause 751 | */ 752 | public function delete_by() 753 | { 754 | $where = func_get_args(); 755 | $this->_set_where($where); 756 | 757 | return $this->_delete($where); 758 | } 759 | 760 | /** 761 | * Delete many rows from the database table by multiple primary values 762 | */ 763 | public function delete_many($primary_values, $time='NOW()') 764 | { 765 | $this->_database->where_in($this->primary_key, $primary_values); 766 | 767 | return $this->_delete($primary_values, $time); 768 | } 769 | 770 | 771 | /** 772 | * Truncates the table 773 | */ 774 | public function truncate() 775 | { 776 | $result = $this->_database->truncate($this->_table); 777 | 778 | return $result; 779 | } 780 | 781 | /* -------------------------------------------------------------- 782 | * RELATIONSHIPS 783 | * ------------------------------------------------------------ */ 784 | 785 | public function with($relationship) 786 | { 787 | $this->_with[] = $relationship; 788 | 789 | if (!$this->is_subscribed('after_get', 'relate')) 790 | { 791 | $this->subscribe('after_get', 'relate'); 792 | } 793 | 794 | return $this; 795 | } 796 | 797 | public function relate($row) 798 | { 799 | if (empty($row)) 800 | { 801 | return $row; 802 | } 803 | 804 | foreach ($this->belongs_to as $key => $value) 805 | { 806 | if (is_string($value)) 807 | { 808 | $relationship = $value; 809 | $options = array( 'primary_key' => $value . '_id', 'model' => $value . '_model' ); 810 | } 811 | else 812 | { 813 | $relationship = $key; 814 | $options = $value; 815 | } 816 | 817 | if (in_array($relationship, $this->_with)) 818 | { 819 | $this->load->model($options['model'], $relationship . '_model'); 820 | 821 | if (is_object($row)) 822 | { 823 | $row->{$relationship} = $this->{$relationship . '_model'}->get($row->{$options['primary_key']}); 824 | } 825 | else 826 | { 827 | $row[$relationship] = $this->{$relationship . '_model'}->get($row[$options['primary_key']]); 828 | } 829 | } 830 | } 831 | 832 | foreach ($this->has_many as $key => $value) 833 | { 834 | if (is_string($value)) 835 | { 836 | $relationship = $value; 837 | $options = array( 'primary_key' => singular($this->_table) . '_id', 'model' => singular($value) . '_model' ); 838 | } 839 | else 840 | { 841 | $relationship = $key; 842 | $options = $value; 843 | } 844 | 845 | if (in_array($relationship, $this->_with)) 846 | { 847 | $this->load->model($options['model'], $relationship . '_model'); 848 | 849 | if (is_object($row)) 850 | { 851 | $row->{$relationship} = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row->{$this->primary_key}); 852 | } 853 | else 854 | { 855 | $row[$relationship] = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row[$this->primary_key]); 856 | } 857 | } 858 | } 859 | 860 | return $row; 861 | } 862 | 863 | /* -------------------------------------------------------------- 864 | * UTILITY METHODS 865 | * ------------------------------------------------------------ */ 866 | 867 | /** 868 | * Retrieve and generate a form_dropdown friendly array 869 | */ 870 | function dropdown() 871 | { 872 | $args = func_get_args(); 873 | 874 | if(count($args) == 2) 875 | { 876 | list($key, $value) = $args; 877 | } 878 | else 879 | { 880 | $key = $this->primary_key; 881 | $value = $args[0]; 882 | } 883 | 884 | $this->trigger('before_dropdown', array( $key, $value )); 885 | 886 | $this->apply_soft_delete_filter(); 887 | 888 | $result = $this->_database->select(array($key, $value)) 889 | ->get($this->_table) 890 | ->result(); 891 | 892 | $options = array(); 893 | 894 | foreach ($result as $row) 895 | { 896 | $options[$row->{$key}] = $row->{$value}; 897 | } 898 | 899 | $options = $this->trigger('after_dropdown', $options); 900 | 901 | return $options; 902 | } 903 | 904 | /*--------------------------- 905 | *Event Callback functions 906 | *---------------------------*/ 907 | protected function subscribe($event, $observer, $handler = FALSE) 908 | { 909 | if (!isset($this->event_listeners[$event])) { 910 | $this->event_listeners[$event] = array(); 911 | } 912 | 913 | if (is_string($handler) || (is_string($observer) && !$handler)) { 914 | $handler = !$handler ? $observer : $handler; 915 | $this->event_listeners[$event][$handler] = $observer; 916 | return $this; 917 | } 918 | 919 | $strategy = $handler ? 'array_unshift' : 'array_push'; 920 | 921 | $strategy($this->event_listeners[$event], $observer); 922 | 923 | return $this; 924 | } 925 | 926 | /** 927 | * @param mixed $database 928 | * 929 | * @return $this 930 | */ 931 | public function set_database($database = null) 932 | { 933 | switch (true) { 934 | case ($database === null) : 935 | $this->_database = $this->db; 936 | break; 937 | case is_string($database) : 938 | $this->_database = $this->_load_database_by_group($database); 939 | break; 940 | case ($database instanceof CI_DB_driver): 941 | $this->_database = $database; 942 | break; 943 | default : 944 | $this->_show_error('You have specified an invalid database connection/group.'); 945 | } 946 | 947 | return $this; 948 | } 949 | 950 | private function _load_database_by_group($group) 951 | { 952 | if (!isset(self::$_connection_cache[$group])) { 953 | self::$_connection_cache[$group] = $this->load->database($group); 954 | } 955 | 956 | return self::$_connection_cache[$group]; 957 | } 958 | 959 | protected function unsubscribe($event, $handler) 960 | { 961 | if (!isset($this->event_listeners[$event][$handler])) { 962 | unset($this->event_listeners[$event][$handler]); 963 | } 964 | 965 | return $this; 966 | } 967 | 968 | protected function is_subscribed($event, $handler) 969 | { 970 | return isset($this->event_listeners[$event][$handler]); 971 | } 972 | 973 | /** 974 | * Fetch a count of rows based on an arbitrary WHERE call. 975 | */ 976 | public function count_by() 977 | { 978 | $where = func_get_args(); 979 | $this->_set_where($where); 980 | $this->apply_soft_delete_filter(); 981 | 982 | return $this->_database->count_all_results($this->_table); 983 | } 984 | 985 | /** 986 | * Fetch a total count of rows, disregarding any previous conditions 987 | */ 988 | public function count_all() 989 | { 990 | $this->apply_soft_delete_filter(); 991 | 992 | return $this->_database->count_all($this->_table); 993 | } 994 | 995 | /** 996 | * Tell the class to skip the insert validation 997 | */ 998 | public function skip_validation() 999 | { 1000 | $this->skip_validation = TRUE; 1001 | 1002 | return $this; 1003 | } 1004 | 1005 | /** 1006 | * Tell the class to skip the insert validation 1007 | */ 1008 | public function enable_validation() 1009 | { 1010 | $this->skip_validation = FALSE; 1011 | 1012 | return $this; 1013 | } 1014 | 1015 | /** 1016 | * Return the next auto increment of the table. Only tested on MySQL. 1017 | */ 1018 | public function get_next_id() 1019 | { 1020 | return (int) $this->_database->select('AUTO_INCREMENT') 1021 | ->from('information_schema.TABLES') 1022 | ->where('TABLE_NAME', $this->_database->dbprefix($this->get_table())) 1023 | ->where('TABLE_SCHEMA', $this->_database->database)->get()->row()->AUTO_INCREMENT; 1024 | } 1025 | 1026 | /** 1027 | * Getter for the table name 1028 | */ 1029 | public function get_table() 1030 | { 1031 | return $this->_table; 1032 | } 1033 | 1034 | /** 1035 | * Getter for the primary key 1036 | */ 1037 | public function primary_key() 1038 | { 1039 | return $this->primary_key; 1040 | } 1041 | 1042 | /** 1043 | * @param $sql 1044 | * 1045 | * @return mixed 1046 | */ 1047 | public function execute_query($sql) 1048 | { 1049 | return $this->_database->query($sql); 1050 | } 1051 | 1052 | /** 1053 | * @return mixed 1054 | */ 1055 | public function get_last_query() 1056 | { 1057 | return $this->_database->last_query(); 1058 | } 1059 | 1060 | /** 1061 | * Alias for db->insert_string() 1062 | * @param $data 1063 | * 1064 | * @return mixed 1065 | */ 1066 | public function get_insert_string($data) 1067 | { 1068 | return $this->_database->insert_string($this->get_table(), $data); 1069 | } 1070 | 1071 | /** 1072 | * @return int 1073 | */ 1074 | public function get_num_rows() 1075 | { 1076 | return $this->num_rows; 1077 | } 1078 | 1079 | /** 1080 | * @return null|int 1081 | */ 1082 | public function get_insert_id() 1083 | { 1084 | return $this->_database->insert_id(); 1085 | } 1086 | 1087 | /** 1088 | * @return null|mix 1089 | */ 1090 | public function get_affected_rows() 1091 | { 1092 | return $this->_database->affected_rows(); 1093 | } 1094 | 1095 | 1096 | /* -------------------------------------------------------------- 1097 | * GLOBAL SCOPES 1098 | * ------------------------------------------------------------ */ 1099 | 1100 | /** 1101 | * Return the next call as an array rather than an object 1102 | */ 1103 | public function as_array() 1104 | { 1105 | $this->_temporary_return_type = 'array'; 1106 | return $this; 1107 | } 1108 | 1109 | /** 1110 | * Return the next call as an object rather than an array 1111 | */ 1112 | public function as_object() 1113 | { 1114 | $this->_temporary_return_type = 'object'; 1115 | return $this; 1116 | } 1117 | 1118 | /** 1119 | * Don't care about soft deleted rows on the next call 1120 | */ 1121 | public function with_deleted() 1122 | { 1123 | $this->_temporary_with_deleted = TRUE; 1124 | return $this; 1125 | } 1126 | 1127 | /** 1128 | * Only get deleted rows on the next call 1129 | */ 1130 | public function only_deleted() 1131 | { 1132 | $this->_temporary_only_deleted = TRUE; 1133 | return $this; 1134 | } 1135 | 1136 | /* -------------------------------------------------------------- 1137 | * OBSERVERS 1138 | * ------------------------------------------------------------ */ 1139 | 1140 | /** 1141 | * MySQL DATETIME created_at and updated_at 1142 | */ 1143 | public function created_at($row) 1144 | { 1145 | if (is_object($row)) 1146 | { 1147 | $row->{$this->created_at_key} = date('Y-m-d H:i:s'); 1148 | } 1149 | else 1150 | { 1151 | $row[$this->created_at_key] = date('Y-m-d H:i:s'); 1152 | } 1153 | 1154 | return $row; 1155 | } 1156 | 1157 | public function updated_at($row) 1158 | { 1159 | if (is_object($row)) 1160 | { 1161 | $row->{$this->updated_at_key} = date('Y-m-d H:i:s'); 1162 | } 1163 | else 1164 | { 1165 | $row[$this->updated_at_key] = date('Y-m-d H:i:s'); 1166 | } 1167 | 1168 | return $row; 1169 | } 1170 | 1171 | 1172 | public function created_by($row) 1173 | { 1174 | if (is_object($row)) 1175 | { 1176 | $row->{$this->created_by_key} = $this->get_current_user(); 1177 | } 1178 | else 1179 | { 1180 | $row[$this->created_by_key] = $this->get_current_user(); 1181 | } 1182 | 1183 | return $row; 1184 | } 1185 | 1186 | public function updated_by($row) 1187 | { 1188 | if (is_object($row)) 1189 | { 1190 | $row->{$this->updated_by_key} = $this->get_current_user(); 1191 | } 1192 | else 1193 | { 1194 | $row[$this->updated_by_key] = $this->get_current_user(); 1195 | } 1196 | 1197 | return $row; 1198 | } 1199 | 1200 | public function update_deleted_by($id) 1201 | { 1202 | $this->_database->set($this->deleted_by_key, $this->get_current_user()); 1203 | return $id; 1204 | } 1205 | 1206 | /** 1207 | * Serialises data for you automatically, allowing you to pass 1208 | * through objects and let it handle the serialisation in the background 1209 | */ 1210 | public function serialize_row($row) 1211 | { 1212 | foreach ($this->callback_parameters as $column) 1213 | { 1214 | $row[$column] = serialize($row[$column]); 1215 | } 1216 | 1217 | return $row; 1218 | } 1219 | 1220 | public function unserialize_row($row) 1221 | { 1222 | foreach ($this->callback_parameters as $column) 1223 | { 1224 | if (is_array($row)) 1225 | { 1226 | $row[$column] = unserialize($row[$column]); 1227 | } 1228 | else 1229 | { 1230 | $row->$column = unserialize($row->$column); 1231 | } 1232 | } 1233 | 1234 | return $row; 1235 | } 1236 | 1237 | /** 1238 | * Protect attributes by removing them from $row array 1239 | */ 1240 | public function protect_attributes($row) 1241 | { 1242 | foreach ($this->protected_attributes as $attr) 1243 | { 1244 | if (is_object($row)) 1245 | { 1246 | unset($row->$attr); 1247 | } 1248 | else 1249 | { 1250 | unset($row[$attr]); 1251 | } 1252 | } 1253 | 1254 | return $row; 1255 | } 1256 | 1257 | /* -------------------------------------------------------------- 1258 | * QUERY BUILDER DIRECT ACCESS METHODS 1259 | * ------------------------------------------------------------ */ 1260 | 1261 | /** 1262 | * A wrapper to $this->_database->order_by() 1263 | * 1264 | * call the ci->db->order_by method as per provided param 1265 | * The param can be string just like default order_by function expect 1266 | * or can be array with set of param!! 1267 | *
1268 |      * $model->order_by('fieldName DESC');
1269 |      * or
1270 |      * $model->order_by(array('fieldName','DESC'));
1271 |      * or
1272 |      * $model->order_by(array('fieldName'=>'DESC', 'fieldName2'=>'ASC'));
1273 |      * or
1274 |      * $model->order_by(array(array('fieldName','DESC'),'fieldName DESC'));
1275 |      * 
1276 | * 1277 | * @param $criteria 1278 | * @param string $order 1279 | * @internal param mixed $orders 1280 | * 1281 | * @return bool 1282 | */ 1283 | public function order_by($criteria, $order = null) 1284 | { 1285 | 1286 | if ($criteria == NULL) { 1287 | return $this; 1288 | } 1289 | 1290 | if (is_array($criteria)) { //Multiple order by provided! 1291 | //check if we got single order by passed as array!! 1292 | if (isset($criteria[1]) && (strtolower($criteria[1]) == 'asc' || strtolower($criteria[1]) == 'desc' || strtolower($criteria[1]) == 'random')) { 1293 | $this->_database->order_by($criteria[0], $criteria[1]); 1294 | return $this; 1295 | } 1296 | foreach ($criteria as $key => $value) 1297 | { 1298 | if(is_array($value)){ 1299 | $this->order_by($value); 1300 | }else{ 1301 | $order_criteria = is_int($key) ? $value : $key; 1302 | $lower_key = strtolower($value); 1303 | $order = ($lower_key == 'asc' || $lower_key == 'desc' || $lower_key == 'random') ? $value : null; 1304 | $this->_database->order_by($order_criteria, $order); 1305 | } 1306 | } 1307 | 1308 | return $this; 1309 | } 1310 | 1311 | $this->_database->order_by($criteria, $order); //its a string just call db order_by 1312 | 1313 | return $this; 1314 | } 1315 | 1316 | /** 1317 | * A wrapper to $this->_database->limit() 1318 | */ 1319 | public function limit($limit, $offset = 0) 1320 | { 1321 | $this->_database->limit($limit, $offset); 1322 | return $this; 1323 | } 1324 | 1325 | /** 1326 | * @param null|string|array $conditions 1327 | * @param string $fields 1328 | * @param null|string|array $order 1329 | * @param int $start 1330 | * @param null|int $limit 1331 | * 1332 | * @return mixed 1333 | */ 1334 | public function find_all($conditions = NULL, $fields = '*', $order = NULL, $start = 0, $limit = NULL) 1335 | { 1336 | if ($conditions != NULL) { 1337 | if (is_array($conditions)) { 1338 | $this->_database->where($conditions); 1339 | } else { 1340 | $this->_database->where($conditions, NULL, FALSE); 1341 | } 1342 | } 1343 | 1344 | if ($fields != NULL) { 1345 | $this->_database->select($fields); 1346 | } 1347 | 1348 | if ($order != NULL) { 1349 | $this->order_by($order); 1350 | } 1351 | 1352 | if ($limit != NULL) { 1353 | $this->_database->limit($limit, $start); 1354 | } 1355 | 1356 | return $this->get_all(); 1357 | } 1358 | 1359 | 1360 | /** 1361 | * @param null|string|array $conditions 1362 | * @param string $fields 1363 | * @param null|string|array $order 1364 | * 1365 | * @return bool 1366 | */ 1367 | public function find($conditions = NULL, $fields = '*', $order = NULL) 1368 | { 1369 | $data = $this->find_all($conditions, $fields, $order, 0, 1); 1370 | 1371 | if ($data) { 1372 | return $data[0]; 1373 | } else { 1374 | return FALSE; 1375 | } 1376 | } 1377 | 1378 | /** 1379 | * @param null $conditions 1380 | * @param $name 1381 | * @param string $fields 1382 | * @param null $order 1383 | * 1384 | * @return bool 1385 | */ 1386 | public function field($conditions = NULL, $name, $fields = '*', $order = NULL) 1387 | { 1388 | $data = $this->find_all($conditions, $fields, $order, 0, 1); 1389 | 1390 | if ($data) { 1391 | $row = $data[0]; 1392 | if (isset($row[$name])) { 1393 | return $row[$name]; 1394 | } 1395 | } 1396 | 1397 | return FALSE; 1398 | } 1399 | 1400 | /** 1401 | * Alias of count_by 1402 | * @param null $conditions 1403 | * 1404 | * @return integer 1405 | */ 1406 | public function find_count($conditions = NULL) 1407 | { 1408 | return $this->count_by($conditions); 1409 | } 1410 | 1411 | /* -------------------------------------------------------------- 1412 | * INTERNAL METHODS 1413 | * ------------------------------------------------------------ */ 1414 | 1415 | 1416 | /** 1417 | * Trigger an event and call its observers. Pass through the event name 1418 | * (which looks for an instance variable $this->event_listeners[event_name] or $this->event_name), an array of 1419 | * parameters to pass through and an optional 'last in iteration' boolean 1420 | * 1421 | * @param $event 1422 | * @param bool|array|void|int $data 1423 | * @param bool $last 1424 | * @return bool|mixed 1425 | */ 1426 | public function trigger($event, $data = FALSE, $last = TRUE) 1427 | { 1428 | if (isset($this->event_listeners[$event]) && is_array($this->event_listeners[$event])) { 1429 | $data = $this->_trigger_event($this->event_listeners[$event], $data, $last); 1430 | }elseif (isset($this->$event) && is_array($this->$event)){ 1431 | $data = $this->_trigger_event($this->$event, $data, $last); 1432 | } 1433 | 1434 | return $data; 1435 | } 1436 | 1437 | private function _trigger_event($event_listeners, $data, $last) 1438 | { 1439 | foreach ($event_listeners as $method) { 1440 | if (is_string($method) && strpos($method, '(')) { 1441 | preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); 1442 | 1443 | $method = $matches[1]; 1444 | $this->callback_parameters = explode(',', $matches[3]); 1445 | } 1446 | 1447 | $callable = $this->getCallableFunction($method); 1448 | 1449 | if (!$callable) { 1450 | $this->callback_parameters = array(); 1451 | continue; 1452 | } 1453 | 1454 | $data = call_user_func_array($callable, array($data, $last)); 1455 | } 1456 | 1457 | return $data; 1458 | } 1459 | 1460 | 1461 | /** 1462 | * Get callable as per given method 1463 | * 1464 | * @param $method 1465 | * @return array|bool|callable 1466 | */ 1467 | private function getCallableFunction($method) 1468 | { 1469 | if (is_callable($method)) { 1470 | return $method; 1471 | } 1472 | 1473 | if (is_string($method) && is_callable(array($this, $method))) { 1474 | return array($this, $method); 1475 | } 1476 | 1477 | return FALSE; 1478 | } 1479 | 1480 | /** 1481 | * filter data as per delete status 1482 | */ 1483 | protected function apply_soft_delete_filter() 1484 | { 1485 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) { 1486 | if($this->_temporary_only_deleted) 1487 | { 1488 | $where = "`{$this->deleted_at_key}` <= NOW()"; 1489 | } 1490 | else 1491 | { 1492 | $where = sprintf('(%1$s > NOW() OR %1$s IS NULL OR %1$s = \'0000-00-00 00:00:00\')', $this->deleted_at_key); 1493 | } 1494 | 1495 | $this->_database->where($where); 1496 | } 1497 | } 1498 | 1499 | 1500 | /** 1501 | * Run validation on the passed data 1502 | */ 1503 | public function validate($data) 1504 | { 1505 | if($this->skip_validation) 1506 | { 1507 | return $data; 1508 | } 1509 | 1510 | if(!empty($this->validate)) 1511 | { 1512 | foreach($data as $key => $val) 1513 | { 1514 | $_POST[$key] = $val; 1515 | } 1516 | 1517 | $this->load->library('form_validation'); 1518 | 1519 | if(is_array($this->validate)) 1520 | { 1521 | $this->form_validation->set_rules($this->validate); 1522 | 1523 | if ($this->form_validation->run() === TRUE) 1524 | { 1525 | return $data; 1526 | } 1527 | else 1528 | { 1529 | return FALSE; 1530 | } 1531 | } 1532 | else 1533 | { 1534 | if ($this->form_validation->run($this->validate) === TRUE) 1535 | { 1536 | return $data; 1537 | } 1538 | else 1539 | { 1540 | return FALSE; 1541 | } 1542 | } 1543 | } 1544 | else 1545 | { 1546 | return $data; 1547 | } 1548 | } 1549 | 1550 | /** 1551 | * Guess the table name by pluralising the model name 1552 | */ 1553 | private function _fetch_table() 1554 | { 1555 | if ($this->_table == NULL) 1556 | { 1557 | $this->_table = plural(preg_replace('/(_m|_model)?$/', '', strtolower(get_class($this)))); 1558 | } 1559 | } 1560 | 1561 | /** 1562 | * Guess the primary key for current table 1563 | */ 1564 | protected function _fetch_primary_key() 1565 | { 1566 | if($this->is_base_model_instance()) { 1567 | return; 1568 | } 1569 | 1570 | if ($this->primary_key == NULL && $this->_database) { 1571 | $this->primary_key = $this->execute_query("SHOW KEYS FROM `" . $this->_database->dbprefix($this->_table) . "` WHERE Key_name = 'PRIMARY'")->row()->Column_name; 1572 | } 1573 | } 1574 | 1575 | private function is_base_model_instance() 1576 | { 1577 | if($this->_base_model_instance == null){ 1578 | $subclass_prefix = $this->config->item('subclass_prefix'); 1579 | $this->_base_model_instance = get_class($this) == $subclass_prefix . "Model"; 1580 | } 1581 | 1582 | return $this->_base_model_instance; 1583 | } 1584 | 1585 | public function get_fields() 1586 | { 1587 | if (empty($this->_fields) && $this->_database) { 1588 | $this->_fields = (array)$this->_database->list_fields($this->_table); 1589 | } 1590 | 1591 | return $this->_fields; 1592 | } 1593 | 1594 | protected function isFieldExist($field) 1595 | { 1596 | return in_array($field, $this->get_fields()); 1597 | } 1598 | 1599 | protected function _guess_is_soft_deletable() 1600 | { 1601 | if ($this->soft_delete === NULL) { 1602 | $this->soft_delete = $this->isFieldExist($this->deleted_at_key); 1603 | } 1604 | 1605 | if (!$this->soft_delete) { 1606 | return; 1607 | } 1608 | 1609 | if ($this->isFieldExist($this->deleted_by_key) && $this->get_current_user()) { 1610 | $this->subscribe('before_delete', 'update_deleted_by','update_deleted_by'); 1611 | } 1612 | } 1613 | 1614 | 1615 | protected function _guess_is_blamable() 1616 | { 1617 | if ($this->blamable === NULL) { 1618 | $this->blamable = $this->isFieldExist($this->created_by_key) 1619 | && $this->isFieldExist($this->updated_by_key) 1620 | && $this->get_current_user(); 1621 | } 1622 | 1623 | if ($this->timestampable) { 1624 | $this->subscribe('before_create', 'created_by'); 1625 | $this->subscribe('before_create', 'updated_by'); 1626 | $this->subscribe('before_update', 'updated_by'); 1627 | } 1628 | } 1629 | 1630 | protected function _guess_is_timestampable() 1631 | { 1632 | if ($this->timestampable === NULL) { 1633 | $this->timestampable = $this->isFieldExist($this->created_at_key) 1634 | && $this->isFieldExist($this->updated_at_key); 1635 | } 1636 | 1637 | if ($this->timestampable) { 1638 | $this->subscribe('before_create', 'created_at'); 1639 | $this->subscribe('before_create', 'updated_at'); 1640 | $this->subscribe('before_update', 'updated_at'); 1641 | } 1642 | } 1643 | 1644 | /** 1645 | * Set WHERE parameters, cleverly 1646 | * @param $params 1647 | */ 1648 | protected function _set_where($params) 1649 | { 1650 | if (count($params) == 1) 1651 | { 1652 | $this->_database->where($params[0]); 1653 | } 1654 | else if(count($params) == 2) 1655 | { 1656 | $this->_database->where($params[0], $params[1]); 1657 | } 1658 | else if(count($params) == 3) 1659 | { 1660 | $this->_database->where($params[0], $params[1], $params[2]); 1661 | } 1662 | else 1663 | { 1664 | $this->_database->where($params); 1665 | } 1666 | } 1667 | 1668 | /** 1669 | * Return the method name for the current return type 1670 | * @param bool $multi 1671 | * @return string 1672 | */ 1673 | protected function _get_return_type_method($multi = FALSE) 1674 | { 1675 | $method = ($multi) ? 'result' : 'row'; 1676 | return $this->_temporary_return_type == 'array' ? $method . '_array' : $method; 1677 | } 1678 | 1679 | 1680 | /** 1681 | * you Must implement this function in MY_Model Class to use blamable an deleted_by feature 1682 | */ 1683 | protected function get_current_user() 1684 | { 1685 | return false; 1686 | } 1687 | 1688 | /** 1689 | * @param $data 1690 | * @return mixed 1691 | */ 1692 | private function _update($data) 1693 | { 1694 | $result = $this->_database->set($data) 1695 | ->update($this->_table); 1696 | $this->trigger('after_update', array($data, $result)); 1697 | 1698 | return $result; 1699 | } 1700 | 1701 | /** 1702 | * @param $msg 1703 | */ 1704 | private function _show_error($msg) 1705 | { 1706 | if (function_exists('show_error')) { 1707 | show_error($msg); 1708 | } 1709 | } 1710 | 1711 | /** 1712 | * @param $data 1713 | * @return bool|mixed 1714 | */ 1715 | private function _do_pre_update($data) 1716 | { 1717 | $data = $this->validate($data); 1718 | $data = $this->trigger('before_update', $data); 1719 | 1720 | return $data; 1721 | } 1722 | 1723 | /** 1724 | * @param $data 1725 | * @return bool|mixed 1726 | */ 1727 | private function _do_pre_create($data) 1728 | { 1729 | $data = $this->validate($data); 1730 | $data = $this->trigger('before_create', $data); 1731 | 1732 | return $data; 1733 | } 1734 | 1735 | /** 1736 | * @param $methodName 1737 | * @return null 1738 | */ 1739 | private function _handle_exception($methodName) 1740 | { 1741 | if (!function_exists('_exception_handler')) { 1742 | return null; 1743 | } 1744 | 1745 | $trace = debug_backtrace(null, 1); 1746 | $errMsg = 'Undefined method : ' . get_class($this) . "::" . $methodName . " called"; 1747 | _exception_handler(E_USER_NOTICE, $errMsg, $trace[0]['file'], $trace[0]['line']); 1748 | } 1749 | } --------------------------------------------------------------------------------