-
Zeff Junior Member
 
-
Posts: 32
Threads: 11
Joined: Nov 2014
Reputation:
0
10-21-2015, 02:52 AM
(This post was last modified: 10-22-2015, 01:58 AM by Zeff.)
Hi,
I created the following library, meant to be used in models so query results can be returned in different formats:
PHP Code: <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
# /application/libraries/Return_object.php
Class Return_object { private $result; private $ci; public function __construct() { $this->ci = &get_instance(); $this->result = new StdClass(); $this->result->data = null; $this->result->status = false; $this->result->count = 0; $this->result->feedback = ''; } public function output($data=null, $response_type='', $params=array()) { // Get the data property if($data && is_object($data)) { $this->result->status = true; if($data->num_rows() > 0) { $this->result->feedback = $this->ci->db->last_query(); $this->result->data = $data->result_object(); $this->result->count = $data->num_rows(); } elseif($data->num_rows == 0) { $this->result->feedback = 'Nothing returned'; } } // Set the callback (note: if JSONP without callback requested, an error will already be thrown by sanitize) $callback = isset($params['callback']) ? $params['callback'] : null; // Handle response type and return object $response_type = strtoupper($response_type); switch ($response_type) { case 'JSON': return json_encode($this->result); break; case 'JSONP': // JSON with Padding, to overcome cross-domain restrictions return $callback . '(' . json_encode($this->result) . ');'; break; case 'ARRAY': return (array) $this->result; break; default: return $this->result; } } public function __destruct() { unset($this->result); } }
So, I use the library in models like this:
First model: ra_promotors_model.php
PHP Code: <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
# /application/models/Ra_promotors_model.php
class Ra_promotors_model extends CI_Model { private $table = 'ra_promotors';
public function __construct() { parent::__construct(); $this->load->database('assets'); $this->load->library('return_object'); } public function get() { $query = $this->db->get($this->table); return $this->return_object->output($query); } } ?>
Second model: ra_users_model.php
PHP Code: <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
# /application/models/Ra_users_model.php
class Ra_users_model extends CI_Model { private $table = 'ra_users';
public function __construct() { parent::__construct(); $this->load->database('assets'); $this->load->library('return_object'); } public function get() { $query = $this->db->get($this->table); return $this->return_object->output($query); } } ?>
In a controller the model now returns a neat object, with a data property, a status property and as feedback, the last performed query...
Code: $this->load->model('ra_promotors', 'rap_model');
$temp['promotors'] = $this->rap_model->get();
Gives the following result in the browser (see attached image)
Everything works fine until two models come into play:
Code: $this->load->model('ra_promotors', 'rap_model');
$temp['promotors'] = $this->rap_model->get();
$this->load->model('ra_users', 'rau_model');
$temp['users'] = $this->rau_model->get();
Now, the two parts of the array are displayed BUT I get the result of the 'ra_promors_model' twice!!!
If I do NOT USE the return object library, I get the two different query results - as expected-...
So it has something to do with the library I created :-(
Anyone?
Thanks for the help!
Zelf
-
mwhitney Posting Freak
    
-
Posts: 1,101
Threads: 4
Joined: Nov 2014
Reputation:
95
When you load the library, if it has already been loaded previously, the existing instance of the library is returned, and the constructor is not executed again.
In both cases, you return $this->result, which is a property which has been assigned an instance of stdClass. Since this is a class, when you assign the result of $this->return_object->output($query) to a variable (or, in your case, an index in an array), you're creating a reference to the instance of $this->result in $this->return_object.
Honestly, I'm not 100% sure why you would get the first one twice, rather than the second one twice, unless the second one didn't return a result.
You should be able to fix this by using `clone` when returning $this->result from output(), or by defining $this->result as an array, then casting it to an object as needed. You may also want to reset $this->result in output(), since anything you don't set will still be set to the value from the previous call, so, currently:
- if you pass a falsey value for $data, everything in $this->result will be from the previous call
- if you pass a truthy object for $data, but num_rows() is less than or equal to 0 for some reason, $this->result->data and $this->result->count will be from the previous call
Also, note that last_query() doesn't work if the current database configuration has save_queries disabled.
-
Zeff Junior Member
 
-
Posts: 32
Threads: 11
Joined: Nov 2014
Reputation:
0
(10-21-2015, 12:43 PM)mwhitney Wrote: When you load the library, if it has already been loaded previously, the existing instance of the library is returned, and the constructor is not executed again.
In both cases, you return $this->result, which is a property which has been assigned an instance of stdClass. Since this is a class, when you assign the result of $this->return_object->output($query) to a variable (or, in your case, an index in an array), you're creating a reference to the instance of $this->result in $this->return_object.
Honestly, I'm not 100% sure why you would get the first one twice, rather than the second one twice, unless the second one didn't return a result.
You should be able to fix this by using `clone` when returning $this->result from output(), or by defining $this->result as an array, then casting it to an object as needed. You may also want to reset $this->result in output(), since anything you don't set will still be set to the value from the previous call, so, currently:
- if you pass a falsey value for $data, everything in $this->result will be from the previous call
- if you pass a truthy object for $data, but num_rows() is less than or equal to 0 for some reason, $this->result->data and $this->result->count will be from the previous call
Also, note that last_query() doesn't work if the current database configuration has save_queries disabled.
Hello mwhitney,
Thanks for your answer. I thought, each time a model is loaded, a separate instance of return_object 'lives' in that model (so the constructor of this class is loaded for each instance)...
It seems I will have to enhance my CI-related OOP knowledge
Kind regards,
Zelf
-
mwhitney Posting Freak
    
-
Posts: 1,101
Threads: 4
Joined: Nov 2014
Reputation:
95
It can be a bit tricky at times, but it really comes down to the CI_Controller being a singleton and setting up some simple shortcuts to the CI_Controller instance. The CI_Controller singleton is setup like this (some comments and whitespace removed to trim down the code):
PHP Code: class CI_Controller { /** * Reference to the CI singleton */ private static $instance;
public function __construct() { self::$instance =& $this;
// Assign all the class objects that were instantiated by the // bootstrap file (CodeIgniter.php) to local class variables // so that CI can run as one big super object. foreach (is_loaded() as $var => $class) { $this->$var =& load_class($class); }
$this->load =& load_class('Loader', 'core'); $this->load->initialize(); log_message('info', 'Controller Class Initialized'); }
/** * Get the CI singleton */ public static function &get_instance() { return self::$instance; } }
To ensure the same instance is retrieved in most contexts, CodeIgniter.php defines the global get_instance() function as follows:
PHP Code: // Load the base controller class require_once BASEPATH.'core/Controller.php';
/** * Returns current CI instance object */ function &get_instance() { return CI_Controller::get_instance(); }
When you load a model or library, the loader gets the CI_Controller instance, then checks whether the model/library has already been loaded on that instance. If it has already been loaded, the loader returns without doing anything else, unless you supply an alternate name in the call to the loader. If it has not been loaded, or you supply an alternate name for the library/model, it loads the library/model and assigns it to the CI_Controller instance as a property named with the name of the library/model or the alternate name.
The bulk of the code in CI_Model is a magic method to access the CI_Controller instance if a method or property isn't defined on the model:
PHP Code: class CI_Model { public function __construct() { log_message('info', 'Model Class Initialized'); }
/** * Allows models to access CI's loaded classes using the same * syntax as controllers. */ public function __get($key) { // Debugging note: // If you're here because you're getting an error message // saying 'Undefined Property: system/core/Model.php', it's // most likely a typo in your model code. return get_instance()->$key; } }
|