From 7556ebd2449d8b632f098cd0ba78428d503090ee Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 6 Aug 2014 18:44:53 +0400 Subject: [PATCH 01/16] New undelete function Allows undeletions when soft delete is enabled. --- core/MY_Model.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/MY_Model.php b/core/MY_Model.php index 05add98..78ba093 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -360,6 +360,29 @@ public function delete($id) return $result; } + /** + * Undelete a row from the table by the primary value (only if soft delete is enabled) + */ + public function undelete($id) + { + $this->trigger('before_undelete', $id); + + $this->_database->where($this->primary_key, $id); + + if ($this->soft_delete) + { + $result = $this->_database->update($this->_table, array( $this->soft_delete_key => FALSE )); + } + else + { + $result = false; + } + + $this->trigger('after_undelete', $result); + + return $result; + } + /** * Delete a row from the database table by an arbitrary WHERE clause */ From 606a9734fb6d1eb8789460e97fef2df5743f90e7 Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 6 Aug 2014 18:57:58 +0400 Subject: [PATCH 02/16] New set_restriction function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use-case: controller’s constructor loads a model, adds a restriction (eg. “user_id = 123”), subsequent calls to the model add the restriction to all the queries. Think of it as an easy built-in security which removes the need to add the same where clause again and again on all methods. --- core/MY_Model.php | 62 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/core/MY_Model.php b/core/MY_Model.php index 78ba093..72a91b4 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -41,6 +41,11 @@ class MY_Model extends CI_Model protected $_temporary_with_deleted = FALSE; protected $_temporary_only_deleted = FALSE; + /** + * Support for custom restrictions + */ + protected $restriction = FALSE; + /** * The various callbacks available to the model. Each are * simple lists of method names (methods will be run on $this). @@ -133,10 +138,7 @@ public function get_by() { $where = func_get_args(); - if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) - { - $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } + $this->add_restrictions(); $this->_set_where($where); @@ -182,10 +184,7 @@ public function get_all() { $this->trigger('before_get'); - if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) - { - $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } + $this->add_restrictions(); $result = $this->_database->get($this->_table) ->{$this->_return_type(1)}(); @@ -250,6 +249,8 @@ public function update($primary_value, $data, $skip_validation = FALSE) { $data = $this->trigger('before_update', $data); + $this->add_restrictions(); + if ($skip_validation === FALSE) { $data = $this->validate($data); @@ -278,6 +279,8 @@ public function update_many($primary_values, $data, $skip_validation = FALSE) { $data = $this->trigger('before_update', $data); + $this->add_restrictions(); + if ($skip_validation === FALSE) { $data = $this->validate($data); @@ -307,6 +310,8 @@ public function update_by() $args = func_get_args(); $data = array_pop($args); + $this->add_restrictions(); + $data = $this->trigger('before_update', $data); if ($this->validate($data) !== FALSE) @@ -329,6 +334,9 @@ public function update_by() */ public function update_all($data) { + + $this->add_restrictions(); + $data = $this->trigger('before_update', $data); $result = $this->_database->set($data) ->update($this->_table); @@ -342,6 +350,9 @@ public function update_all($data) */ public function delete($id) { + + $this->add_restrictions(); + $this->trigger('before_delete', $id); $this->_database->where($this->primary_key, $id); @@ -365,6 +376,9 @@ public function delete($id) */ public function undelete($id) { + + $this->add_restrictions(); + $this->trigger('before_undelete', $id); $this->_database->where($this->primary_key, $id); @@ -388,6 +402,9 @@ public function undelete($id) */ public function delete_by() { + + $this->add_restrictions(); + $where = func_get_args(); $where = $this->trigger('before_delete', $where); @@ -414,6 +431,9 @@ public function delete_by() */ public function delete_many($primary_values) { + + $this->add_restrictions(); + $primary_values = $this->trigger('before_delete', $primary_values); $this->_database->where_in($this->primary_key, $primary_values); @@ -574,10 +594,8 @@ function dropdown() */ public function count_by() { - if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) - { - $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } + + $this->add_restrictions(); $where = func_get_args(); $this->_set_where($where); @@ -590,10 +608,8 @@ public function count_by() */ public function count_all() { - if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) - { - $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } + + $this->add_restrictions(); return $this->_database->count_all($this->_table); } @@ -960,4 +976,18 @@ protected function _return_type($multi = FALSE) $method = ($multi) ? 'result' : 'row'; return $this->_temporary_return_type == 'array' ? $method . '_array' : $method; } + + /** + * Add restrictions to query + */ + protected function add_restrictions() { + if ($this->restriction) + { + $this->_database->where($this->restriction); + } + if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + { + $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); + } + } } From a6359e5e82c2e4fc834c7e0206a93a59b1ec964a Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 6 Aug 2014 19:11:42 +0400 Subject: [PATCH 03/16] documentation update --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 38fc601..46bddd4 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ $this->post->insert(array( $this->post->update(1, array( 'status' => 'closed' )); $this->post->delete(1); + +$this->post->undelete(1); // works only if soft delete is enabled (see below) ``` Installation/Usage @@ -288,6 +290,10 @@ If you'd like to include only the columns that have been deleted, you can use th => $this->book_model->only_deleted()->get_by('user_id', 1); -> SELECT * FROM books WHERE user_id = 1 AND deleted = 1 +If you'd like to undelete a previously delete entry, you can use the `undelete()` function: + + => $this->book_model->undelete(1); + Built-in Observers ------------------- @@ -310,6 +316,26 @@ The timestamps (MySQL compatible) `created_at` and `updated_at` are now availabl public $after_get = array( 'unserialize(seat_types)' ); } +Common restrictions +------------------- + +**MY_Model** contains an easy way to restrict all its results. Say you have an admin account which can see everything and user accounts which can see only their results, you can use the following codein your controller: + + class Books extends CI_Controller { + + function __construct() { + parent::__construct(); + + $this->load->model('book_model'); + + if (!$user_is_admin) + { + $this->book_model->set_restriction('book_owner = '.(int)$user_id); + } + } + +Don't forget to assign the correct value for `book_owner` when you create a new record, else the user won't be able to see its book. + Database Connection ------------------- @@ -365,6 +391,10 @@ Other Documentation Changelog --------- +**Version 2.1.0** +* Added support for undeletes when using soft deletes +* Added support for restricting viewable results + **Version 2.0.0** * Added support for soft deletes * Removed Composer support. Great system, CI makes it difficult to use for MY_ classes From fb3c239e4e0ac0d55f54d54d7b8f5af6e626a7cf Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 6 Aug 2014 20:44:03 +0400 Subject: [PATCH 04/16] add_restrictions: conditionally skipping soft_delete skipping soft_delete condition when called from delete (so that we can pass unit tests) --- core/MY_Model.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/core/MY_Model.php b/core/MY_Model.php index 72a91b4..1c90e0c 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -351,7 +351,7 @@ public function update_all($data) public function delete($id) { - $this->add_restrictions(); + $this->add_restrictions(FALSE); $this->trigger('before_delete', $id); @@ -389,7 +389,7 @@ public function undelete($id) } else { - $result = false; + $result = FALSE; } $this->trigger('after_undelete', $result); @@ -403,7 +403,7 @@ public function undelete($id) public function delete_by() { - $this->add_restrictions(); + $this->add_restrictions(FALSE); $where = func_get_args(); @@ -432,7 +432,7 @@ public function delete_by() public function delete_many($primary_values) { - $this->add_restrictions(); + $this->add_restrictions(FALSE); $primary_values = $this->trigger('before_delete', $primary_values); @@ -977,15 +977,23 @@ protected function _return_type($multi = FALSE) return $this->_temporary_return_type == 'array' ? $method . '_array' : $method; } + /** + * Set restriction to be appended to the query + */ + public function set_restriction($restriction) + { + $this->restriction = $restriction; + } + /** * Add restrictions to query */ - protected function add_restrictions() { + protected function add_restrictions($soft_delete_check = TRUE) { if ($this->restriction) { $this->_database->where($this->restriction); } - if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) + if ($soft_delete_check && $this->soft_delete && $this->_temporary_with_deleted !== TRUE) { $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); } From 59d4999b1f4c3781057f6c51225bddd6aeae62eb Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Thu, 7 Aug 2014 15:18:44 +0400 Subject: [PATCH 05/16] Fixes jamierumbelow/codeigniter-base-model#87 --- core/MY_Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/MY_Model.php b/core/MY_Model.php index 1c90e0c..198e93a 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -611,7 +611,7 @@ public function count_all() $this->add_restrictions(); - return $this->_database->count_all($this->_table); + return $this->_database->count_all_results($this->_table); } /** From 8bbda347b5f308779fc11e06dab8c8dced8098bf Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Thu, 7 Aug 2014 15:24:53 +0400 Subject: [PATCH 06/16] Fixes jamierumbelow/codeigniter-base-model#87 Changing unit test following earlier fix --- tests/MY_Model_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MY_Model_test.php b/tests/MY_Model_test.php index bcde95b..99a8a86 100644 --- a/tests/MY_Model_test.php +++ b/tests/MY_Model_test.php @@ -839,7 +839,7 @@ public function test_count_by() public function test_count_all() { $this->model->_database->expects($this->once()) - ->method('count_all') + ->method('count_all_results') ->with($this->equalTo('records')) ->will($this->returnValue(200)); $this->assertEquals($this->model->count_all(), 200); From 53bc1436047b880d4bce772b7db5851f0ebfa74a Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Mon, 11 Aug 2014 14:49:14 +0400 Subject: [PATCH 07/16] [new] without_restriction() [fix] with_deleted() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - like with_deleted() function, without_restriction() temporarly includes restricted rows - with_deleted() was not temporary and was maintained between model calls — not sure if it was on purpose or not, but this is fixed: with_deleted() only works for the current call. --- README.md | 14 +++++++++----- core/MY_Model.php | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 46bddd4..d720e86 100644 --- a/README.md +++ b/README.md @@ -275,17 +275,17 @@ By default, MY_Model expects a `TINYINT` or `INT` column named `deleted`. If you protected $soft_delete_key = 'book_deleted_status'; } -Now, when you make a call to any of the `get_` methods, a constraint will be added to not withdraw deleted columns: +Now, when you make a call to any of the `get_` methods, a constraint will be added to not withdraw deleted rows: => $this->book_model->get_by('user_id', 1); -> SELECT * FROM books WHERE user_id = 1 AND deleted = 0 -If you'd like to include deleted columns, you can use the `with_deleted()` scope: +If you'd like to include deleted rows, you can use the `with_deleted()` scope: => $this->book_model->with_deleted()->get_by('user_id', 1); -> SELECT * FROM books WHERE user_id = 1 -If you'd like to include only the columns that have been deleted, you can use the `only_deleted()` scope: +If you'd like to include only the rows that have been deleted, you can use the `only_deleted()` scope: => $this->book_model->only_deleted()->get_by('user_id', 1); -> SELECT * FROM books WHERE user_id = 1 AND deleted = 1 @@ -336,6 +336,10 @@ Common restrictions Don't forget to assign the correct value for `book_owner` when you create a new record, else the user won't be able to see its book. +If you'd like to include restricted rows, you can use the `without_restriction()` scope: + + => $this->book_model->without_restriction()->get_by('user_id', 1); + Database Connection ------------------- @@ -392,8 +396,8 @@ Changelog --------- **Version 2.1.0** -* Added support for undeletes when using soft deletes -* Added support for restricting viewable results +* Added support for undeletes when using soft deletes (thanks [julienmru](https://github.com/julienmru)!) +* Added support for restricting viewable results (thanks [julienmru](https://github.com/julienmru)!) **Version 2.0.0** * Added support for soft deletes diff --git a/core/MY_Model.php b/core/MY_Model.php index 198e93a..290bfd1 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -45,6 +45,7 @@ class MY_Model extends CI_Model * Support for custom restrictions */ protected $restriction = FALSE; + protected $_temporary_without_restriction = FALSE; /** * The various callbacks available to the model. Each are @@ -985,17 +986,30 @@ public function set_restriction($restriction) $this->restriction = $restriction; } + /** + * Don't care about restrictions on the next call + */ + public function without_restriction() + { + $this->_temporary_without_restriction = TRUE; + return $this; + } + /** * Add restrictions to query */ protected function add_restrictions($soft_delete_check = TRUE) { - if ($this->restriction) + if ($this->_temporary_without_restriction !== TRUE && $this->restriction) { $this->_database->where($this->restriction); + } else { + $this->_temporary_without_restriction = FALSE; } if ($soft_delete_check && $this->soft_delete && $this->_temporary_with_deleted !== TRUE) { $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } + } else { + $this->_temporary_with_deleted = $this->_temporary_only_deleted = FALSE; + } } } From 52b3fa8165ae11ad33c7b9a9635ae62a987169e3 Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 13 Aug 2014 09:39:26 +0400 Subject: [PATCH 08/16] truncate() is aliased to delete() if restrictions enabled if restrictions are enabled, truncate is rewritten to delete so that records which the user has no access to are not deleted --- core/MY_Model.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/core/MY_Model.php b/core/MY_Model.php index 290bfd1..8311a9d 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -459,8 +459,17 @@ public function delete_many($primary_values) */ public function truncate() { - $result = $this->_database->truncate($this->_table); - + if ($this->restriction) + { + $this->add_restrictions(FALSE); + + $result = $this->_database->delete_by("1 = 1"); + } + else + { + $result = $this->_database->truncate($this->_table); + } + return $result; } @@ -1002,13 +1011,17 @@ protected function add_restrictions($soft_delete_check = TRUE) { if ($this->_temporary_without_restriction !== TRUE && $this->restriction) { $this->_database->where($this->restriction); - } else { + } + else + { $this->_temporary_without_restriction = FALSE; } if ($soft_delete_check && $this->soft_delete && $this->_temporary_with_deleted !== TRUE) { $this->_database->where($this->soft_delete_key, (bool)$this->_temporary_only_deleted); - } else { + } + else + { $this->_temporary_with_deleted = $this->_temporary_only_deleted = FALSE; } } From e834fad2945af7e17b3b0a43d911aca4a869e2fe Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Wed, 13 Aug 2014 10:28:19 +0400 Subject: [PATCH 09/16] get_primary_value Possibility to retrieve the current primary key value, eg. in observers. --- README.md | 2 ++ core/MY_Model.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/README.md b/README.md index d720e86..91f86b8 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ Observers can also take parameters in their name, much like CodeIgniter's Form V return $row; } +If you need the primary key value in an observer, you can use `get_primary_value()` utility function. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). + Validation ---------- diff --git a/core/MY_Model.php b/core/MY_Model.php index 8311a9d..08c8ecb 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -32,6 +32,7 @@ class MY_Model extends CI_Model * Used by the get(), update() and delete() functions. */ protected $primary_key = 'id'; + protected $primary_value = FALSE; /** * Support for soft deletes and this model's 'deleted' key @@ -128,6 +129,8 @@ public function __construct() */ public function get($primary_value) { + $this->primary_value = $primary_value; + return $this->get_by($this->primary_key, $primary_value); } @@ -141,6 +144,8 @@ public function get_by() $this->add_restrictions(); + $this->primary_value = FALSE; + $this->_set_where($where); $this->trigger('before_get'); @@ -183,6 +188,9 @@ public function get_many_by() */ public function get_all() { + + $this->primary_value = FALSE; + $this->trigger('before_get'); $this->add_restrictions(); @@ -213,11 +221,16 @@ public function insert($data, $skip_validation = FALSE) if ($data !== FALSE) { + + $this->primary_value = FALSE; + $data = $this->trigger('before_create', $data); $this->_database->insert($this->_table, $data); $insert_id = $this->_database->insert_id(); + $this->primary_value = $insert_id; + $this->trigger('after_create', $insert_id); return $insert_id; @@ -248,6 +261,8 @@ public function insert_many($data, $skip_validation = FALSE) */ public function update($primary_value, $data, $skip_validation = FALSE) { + $this->primary_value = $primary_value; + $data = $this->trigger('before_update', $data); $this->add_restrictions(); @@ -278,6 +293,9 @@ public function update($primary_value, $data, $skip_validation = FALSE) */ public function update_many($primary_values, $data, $skip_validation = FALSE) { + + $this->primary_value = FALSE; + $data = $this->trigger('before_update', $data); $this->add_restrictions(); @@ -313,6 +331,8 @@ public function update_by() $this->add_restrictions(); + $this->primary_value = FALSE; + $data = $this->trigger('before_update', $data); if ($this->validate($data) !== FALSE) @@ -338,6 +358,8 @@ public function update_all($data) $this->add_restrictions(); + $this->primary_value = FALSE; + $data = $this->trigger('before_update', $data); $result = $this->_database->set($data) ->update($this->_table); @@ -352,6 +374,8 @@ public function update_all($data) public function delete($id) { + $this->primary_value = $id; + $this->add_restrictions(FALSE); $this->trigger('before_delete', $id); @@ -380,6 +404,8 @@ public function undelete($id) $this->add_restrictions(); + $this->primary_value = $id; + $this->trigger('before_undelete', $id); $this->_database->where($this->primary_key, $id); @@ -408,6 +434,8 @@ public function delete_by() $where = func_get_args(); + $this->primary_value = FALSE; + $where = $this->trigger('before_delete', $where); $this->_set_where($where); @@ -435,6 +463,8 @@ public function delete_many($primary_values) $this->add_restrictions(FALSE); + $this->primary_value = FALSE; + $primary_values = $this->trigger('before_delete', $primary_values); $this->_database->where_in($this->primary_key, $primary_values); @@ -1025,4 +1055,13 @@ protected function add_restrictions($soft_delete_check = TRUE) { $this->_temporary_with_deleted = $this->_temporary_only_deleted = FALSE; } } + + /** + * Get current primary key value + * (useful for observers which don't pass the primary key, such as after_update) + */ + public function get_primary_value() + { + return $this->primary_value; + } } From 76141535a8d1f4c849b5f22c602da1e94d5a610e Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Thu, 14 Aug 2014 10:39:25 +0400 Subject: [PATCH 10/16] fix in undelete() --- core/MY_Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/MY_Model.php b/core/MY_Model.php index 08c8ecb..491ab30 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -402,7 +402,7 @@ public function delete($id) public function undelete($id) { - $this->add_restrictions(); + $this->add_restrictions(FALSE); $this->primary_value = $id; From 78c871dfc6e1a724a0bc4081399bc0db16919acf Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Thu, 14 Aug 2014 11:08:36 +0400 Subject: [PATCH 11/16] New join() function, fixes jamierumbelow/codeigniter-base-model#92 Adds a method to do a direct ActiveRecord join, eg: $this->photo_model->join('photos_selections', 'ps_photo = p_id', 'left')->get_many_by('p_album', $id) --- README.md | 7 ++++++- core/MY_Model.php | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 91f86b8..02b9a55 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,11 @@ To change this, use the `primary_key` value when configuring: public $has_many = array( 'comments' => array( 'primary_key' => 'parent_post_id' ) ); } +You can also create a more complex join using `join()` method which works exactly as its ActiveRecord counterpart. + + $post = $this->post_model->join('author', 'author_id = post_author', 'left') + ->get(1); + Arrays vs Objects ----------------- @@ -292,7 +297,7 @@ If you'd like to include only the rows that have been deleted, you can use the ` => $this->book_model->only_deleted()->get_by('user_id', 1); -> SELECT * FROM books WHERE user_id = 1 AND deleted = 1 -If you'd like to undelete a previously delete entry, you can use the `undelete()` function: +If you'd like to undelete a previously delete entry, you can use the `undelete()` method: => $this->book_model->undelete(1); diff --git a/core/MY_Model.php b/core/MY_Model.php index 491ab30..b3b0dd5 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -585,6 +585,16 @@ public function relate($row) return $row; } + /** + * Direct ActiveRecord join + */ + public function join($table, $cond, $type = '', $escape = NULL) + { + $this->_database->join($table, $cond, $type, $escape); + + return $this; + } + /* -------------------------------------------------------------- * UTILITY METHODS * ------------------------------------------------------------ */ From b73bfe0cf77c376a2685dee9e449f06d9362f5f8 Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Thu, 14 Aug 2014 21:24:19 +0400 Subject: [PATCH 12/16] Fixes jamierumbelow/codeigniter-base-model#172 and jamierumbelow/codeigniter-base-model#173 Code cleanup --- README.md | 2 ++ core/MY_Model.php | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02b9a55..4cf57d9 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ The full list of observers are as follows: * $after_get * $before_delete * $after_delete +* $before_undelete +* $after_undelete 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: diff --git a/core/MY_Model.php b/core/MY_Model.php index b3b0dd5..f0ef658 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -199,9 +199,11 @@ public function get_all() ->{$this->_return_type(1)}(); $this->_temporary_return_type = $this->return_type; + $last_key = count($result) - 1; + foreach ($result as $key => &$row) { - $row = $this->trigger('after_get', $row, ($key == count($result) - 1)); + $row = $this->trigger('after_get', $row, ($key == $last_key)); } $this->_with = array(); @@ -250,7 +252,7 @@ public function insert_many($data, $skip_validation = FALSE) foreach ($data as $key => $row) { - $ids[] = $this->insert($row, $skip_validation, ($key == count($data) - 1)); + $ids[] = $this->insert($row, $skip_validation); } return $ids; From 27dcafc346110d40bea5a66646457229013d202f Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Mon, 25 Aug 2014 15:19:35 +0400 Subject: [PATCH 13/16] with() inside with() --- README.md | 10 ++++++++++ core/MY_Model.php | 10 +++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cf57d9..7a65eb9 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,16 @@ The related data will be embedded in the returned value from `get`: echo $message; } +You can also access a deeper level of related data by specifying a second parameter to the `with()` method: + + $post = $this->post_model->with('author', 'country') + ->with('comments') + ->get(1); + +Will allow you to use: + + echo $post->author->country->name; + Separate queries will be run to select the data, so where performance is important, a separate JOIN and SELECT call is recommended. The primary key can also be configured. For _belongs\_to_ calls, the related key is on the current object, not the foreign one. Pseudocode: diff --git a/core/MY_Model.php b/core/MY_Model.php index f0ef658..571b33f 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -76,6 +76,7 @@ class MY_Model extends CI_Model protected $has_many = array(); protected $_with = array(); + protected $_subwith = array(); /** * An array of validation rules. This needs to be the same format @@ -509,9 +510,10 @@ public function truncate() * RELATIONSHIPS * ------------------------------------------------------------ */ - public function with($relationship) + public function with($relationship, $subrelationship = FALSE) { $this->_with[] = $relationship; + if ($subrelationship !== FALSE ) $this->_subwith[$relationship] = $subrelationship; if (!in_array('relate', $this->after_get)) { @@ -547,11 +549,13 @@ public function relate($row) if (is_object($row)) { - $row->{$relationship} = $this->{$relationship . '_model'}->get($row->{$options['primary_key']}); + if (isset($this->_subwith[$relationship]) && $this->_subwith[$relationship]) $row->{$relationship} = $this->{$relationship . '_model'}->with($this->_subwith[$relationship])->get($row->{$options['primary_key']}); + else $row->{$relationship} = $this->{$relationship . '_model'}->get($row->{$options['primary_key']}); } else { - $row[$relationship] = $this->{$relationship . '_model'}->get($row[$options['primary_key']]); + if (isset($this->_subwith[$relationship]) && $this->_subwith[$relationship]) $row[$relationship] = $this->{$relationship . '_model'}->with($this->_subwith[$relationship])->get($row[$options['primary_key']]); + else $row[$relationship] = $this->{$relationship . '_model'}->get($row[$options['primary_key']]); } } } From 7ca6900fa4745560bcde0bdd3405a1ec9386c952 Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Fri, 5 Sep 2014 17:03:01 +0400 Subject: [PATCH 14/16] new method: without_callbacks() --- README.md | 4 +++- core/MY_Model.php | 51 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7a65eb9..ffb0876 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,9 @@ Observers can also take parameters in their name, much like CodeIgniter's Form V return $row; } -If you need the primary key value in an observer, you can use `get_primary_value()` utility function. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). +If you need the primary key value in an observer, you can use `get_primary_value()` utility method. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). + +You can temporarly disable callbacks (eg. to get raw MySQL data) using `without_callbacks()` method. Validation ---------- diff --git a/core/MY_Model.php b/core/MY_Model.php index 571b33f..382affc 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -61,6 +61,8 @@ class MY_Model extends CI_Model protected $before_delete = array(); protected $after_delete = array(); + protected $_temporary_without_triggers = FALSE; + protected $callback_parameters = array(); /** @@ -596,9 +598,19 @@ public function relate($row) */ public function join($table, $cond, $type = '', $escape = NULL) { - $this->_database->join($table, $cond, $type, $escape); + $this->_database->join($table, $cond, $type, $escape); - return $this; + return $this; + } + + /** + * Direct ActiveRecord group by + */ + public function group_by($by, $escape = NULL) + { + $this->_database->group_by($by, $escape); + + return $this; } /* -------------------------------------------------------------- @@ -875,22 +887,31 @@ public function limit($limit, $offset = 0) */ public function trigger($event, $data = FALSE, $last = TRUE) { - if (isset($this->$event) && is_array($this->$event)) + if ($this->_temporary_without_triggers) + { + if (strpos($event, 'after_') === 0) $this->_temporary_without_triggers = FALSE; + return $data; + } + else { - foreach ($this->$event as $method) + if (isset($this->$event) && is_array($this->$event)) { - if (strpos($method, '(')) + foreach ($this->$event as $method) { - preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); + if (strpos($method, '(')) + { + preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); - $method = $matches[1]; - $this->callback_parameters = explode(',', $matches[3]); - } + $method = $matches[1]; + $this->callback_parameters = explode(',', $matches[3]); + } - $data = call_user_func_array(array($this, $method), array($data, $last)); + $data = call_user_func_array(array($this, $method), array($data, $last)); + } } } + return $data; } @@ -1080,4 +1101,14 @@ public function get_primary_value() { return $this->primary_value; } + + /** + * Don't issue callbacks on the next call + */ + public function without_callbacks() + { + $this->_temporary_without_triggers = TRUE; + + return $this; + } } From ee5f848c559666ac71312486dc3e4e6041ce7e78 Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Fri, 5 Sep 2014 17:40:06 +0400 Subject: [PATCH 15/16] Revert "new method: without_callbacks()" This reverts commit 7ca6900fa4745560bcde0bdd3405a1ec9386c952. --- README.md | 4 +--- core/MY_Model.php | 51 ++++++++++------------------------------------- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index ffb0876..7a65eb9 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,7 @@ Observers can also take parameters in their name, much like CodeIgniter's Form V return $row; } -If you need the primary key value in an observer, you can use `get_primary_value()` utility method. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). - -You can temporarly disable callbacks (eg. to get raw MySQL data) using `without_callbacks()` method. +If you need the primary key value in an observer, you can use `get_primary_value()` utility function. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). Validation ---------- diff --git a/core/MY_Model.php b/core/MY_Model.php index 382affc..571b33f 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -61,8 +61,6 @@ class MY_Model extends CI_Model protected $before_delete = array(); protected $after_delete = array(); - protected $_temporary_without_triggers = FALSE; - protected $callback_parameters = array(); /** @@ -598,19 +596,9 @@ public function relate($row) */ public function join($table, $cond, $type = '', $escape = NULL) { - $this->_database->join($table, $cond, $type, $escape); + $this->_database->join($table, $cond, $type, $escape); - return $this; - } - - /** - * Direct ActiveRecord group by - */ - public function group_by($by, $escape = NULL) - { - $this->_database->group_by($by, $escape); - - return $this; + return $this; } /* -------------------------------------------------------------- @@ -887,31 +875,22 @@ public function limit($limit, $offset = 0) */ public function trigger($event, $data = FALSE, $last = TRUE) { - if ($this->_temporary_without_triggers) - { - if (strpos($event, 'after_') === 0) $this->_temporary_without_triggers = FALSE; - return $data; - } - else + if (isset($this->$event) && is_array($this->$event)) { - if (isset($this->$event) && is_array($this->$event)) + foreach ($this->$event as $method) { - foreach ($this->$event as $method) + if (strpos($method, '(')) { - if (strpos($method, '(')) - { - preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); - - $method = $matches[1]; - $this->callback_parameters = explode(',', $matches[3]); - } + preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); - $data = call_user_func_array(array($this, $method), array($data, $last)); + $method = $matches[1]; + $this->callback_parameters = explode(',', $matches[3]); } + + $data = call_user_func_array(array($this, $method), array($data, $last)); } } - return $data; } @@ -1101,14 +1080,4 @@ public function get_primary_value() { return $this->primary_value; } - - /** - * Don't issue callbacks on the next call - */ - public function without_callbacks() - { - $this->_temporary_without_triggers = TRUE; - - return $this; - } } From ca82a3ca9f8c9e0c9a80ebbdbf880ed2434950ec Mon Sep 17 00:00:00 2001 From: Julien Tessier Date: Fri, 5 Sep 2014 17:41:39 +0400 Subject: [PATCH 16/16] [new] without_callbacks() --- README.md | 4 ++- core/MY_Model.php | 79 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7a65eb9..ffb0876 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,9 @@ Observers can also take parameters in their name, much like CodeIgniter's Form V return $row; } -If you need the primary key value in an observer, you can use `get_primary_value()` utility function. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). +If you need the primary key value in an observer, you can use `get_primary_value()` utility method. This works only for single-row queries (eg: `get`, `create`, `update`, `delete`). On multi-row queries, it will return FALSE (eg: `get_all`, `delete_by`). + +You can temporarly disable callbacks (eg. to get raw MySQL data) using `without_callbacks()` method. Validation ---------- diff --git a/core/MY_Model.php b/core/MY_Model.php index 571b33f..31b8ba4 100644 --- a/core/MY_Model.php +++ b/core/MY_Model.php @@ -61,6 +61,8 @@ class MY_Model extends CI_Model protected $before_delete = array(); protected $after_delete = array(); + protected $_temporary_without_triggers = FALSE; + protected $callback_parameters = array(); /** @@ -132,7 +134,7 @@ public function get($primary_value) { $this->primary_value = $primary_value; - return $this->get_by($this->primary_key, $primary_value); + return $this->get_by($this->primary_key, $primary_value); } /** @@ -147,7 +149,7 @@ public function get_by() $this->primary_value = FALSE; - $this->_set_where($where); + $this->_set_where($where); $this->trigger('before_get'); @@ -439,7 +441,7 @@ public function delete_by() $this->primary_value = FALSE; - $where = $this->trigger('before_delete', $where); + $where = $this->trigger('before_delete', $where); $this->_set_where($where); @@ -525,9 +527,9 @@ public function with($relationship, $subrelationship = FALSE) public function relate($row) { - if (empty($row)) + if (empty($row)) { - return $row; + return $row; } foreach ($this->belongs_to as $key => $value) @@ -596,9 +598,19 @@ public function relate($row) */ public function join($table, $cond, $type = '', $escape = NULL) { - $this->_database->join($table, $cond, $type, $escape); + $this->_database->join($table, $cond, $type, $escape); + + return $this; + } - return $this; + /** + * Direct ActiveRecord group by + */ + public function group_by($by, $escape = NULL) + { + $this->_database->group_by($by, $escape); + + return $this; } /* -------------------------------------------------------------- @@ -875,22 +887,31 @@ public function limit($limit, $offset = 0) */ public function trigger($event, $data = FALSE, $last = TRUE) { - if (isset($this->$event) && is_array($this->$event)) + if ($this->_temporary_without_triggers) + { + if (strpos($event, 'after_') === 0) $this->_temporary_without_triggers = FALSE; + return $data; + } + else { - foreach ($this->$event as $method) + if (isset($this->$event) && is_array($this->$event)) { - if (strpos($method, '(')) + foreach ($this->$event as $method) { - preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); + if (strpos($method, '(')) + { + preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches); - $method = $matches[1]; - $this->callback_parameters = explode(',', $matches[3]); - } + $method = $matches[1]; + $this->callback_parameters = explode(',', $matches[3]); + } - $data = call_user_func_array(array($this, $method), array($data, $last)); + $data = call_user_func_array(array($this, $method), array($data, $last)); + } } } + return $data; } @@ -996,8 +1017,8 @@ protected function _set_where($params) { $this->_database->where($params[0]); } - else if(count($params) == 2) - { + else if(count($params) == 2) + { if (is_array($params[1])) { $this->_database->where_in($params[0], $params[1]); @@ -1006,11 +1027,11 @@ protected function _set_where($params) { $this->_database->where($params[0], $params[1]); } - } - else if(count($params) == 3) - { - $this->_database->where($params[0], $params[1], $params[2]); - } + } + else if(count($params) == 3) + { + $this->_database->where($params[0], $params[1], $params[2]); + } else { if (is_array($params[1])) @@ -1068,8 +1089,8 @@ protected function add_restrictions($soft_delete_check = TRUE) { } else { - $this->_temporary_with_deleted = $this->_temporary_only_deleted = FALSE; - } + $this->_temporary_with_deleted = $this->_temporary_only_deleted = FALSE; + } } /** @@ -1080,4 +1101,14 @@ public function get_primary_value() { return $this->primary_value; } + + /** + * Don't issue callbacks on the next call + */ + public function without_callbacks() + { + $this->_temporary_without_triggers = TRUE; + + return $this; + } }