A problem haunting me since early days of CodeIgniter and now, with the new CI 3 i want to see if there is a more elegant way to solve it.
// file: application/core/MY_Controller.php
class MY_Controller extends CI_Controller {
public $GLO;
function __construct(){
parent::__construct();
$this->GLO['foo'] = 'bar';
$this->GLO['arr'] = array();
}
}
then, later in the code, I need to get and set the values of the $GLO variable dynamically. So for instance:
// file: application/controllers/dispatcher.php
class Dispatcher extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->model('public/langs');
print_r($this->GLO);
}
}
will print array('foo'=>'bar, 'arr'=>Array()) which is correct. Also in my models I can get the values of the $GLO array in the same manner. However, as soon as I need to set any values in the $GLO array, I get the Indirect modification of overloaded property notice and so I am stuck. In my model (after executing a DB query):
// file: application/models/public/langs.php
class Langs extends CI_Model {
function __construct(){
parent::__construct();
}
function set_global_languages(){
print_r($this->GLO); // <<< prints the same values as in the controller above
$temp = array();
// [stripped db code]
$temp['label'] = $row->label;
$temp['id'] = $row->id;
$this->GLO['arr'][] = $temp; // <<< this is where the notice happens
}
Any clues of how I can use $this->GLO['foo'] = 'baz'; for setting properties of this global array in my models?
Cheers.
I stumbled upon this problem a ton of times. Since you are asking for an elegant solution - i try to give you some idea.
There is a library called Registry, which is written totally fine and can be extended any time. You can find the library here.
Put it in your libraries folder.
This library should get autoloaded so it is always available.
Add it to your config/autoload.php
$autoload['libraries'] = array(...,"Registry");
After that you should have a really easy way of storing things globally within CI - in your code for example it would look like
class MY_Controller extends CI_Controller {
function __construct()
{
parent::__construct();
$arrSomething = [
'foo' => "bar",
"arr" => array()
];
$this->registry->set("arrSomething",$arrSomething);
}
}
class Dispatcher extends MY_Controller
{
function __construct()
{
parent::__construct();
$this->load->model('public/langs');
}
}
class Langs extends CI_Model
{
function __construct(){
parent::__construct();
}
function set_global_languages()
{
$arrSomething = $this->registry->get("arrSomething");
$temp = array();
// [stripped db code]
$temp['label'] = $row->label;
$temp['id'] = $row->id;
$arrSomething['arr'][] = $temp;
//in case of an array i guess you've to reset it because there is no reference or try to call it by reference with "&"
$this->registry->set("arrSomething");
}
}
sintakonte, thanks a lot for the tip. Last night I had to rewrite a lot of code to see if and how it works. The Registry class works as expected! The only issue with this set/get method is the handling of deep arrays, especially those created dynamically and having their data pushed dynamically too. So today morning I did something very bad :) and just created this class:
class Globals {
public $data = array();
}
So now I am going from $this->GLO['foo'] to $this->globals->data['foo'] and get to keep existing syntax intact! It also enables me to reuse the class in different controllers and models under different names, once initializing it as "globals" and the other time as "admin". Seems to work alright, too!
Related
Second update
I think I've been approaching this problem from the wrong side of the coin. Would I be correct in assuming that I should be making 'First' an abstract class and just finding a way to reference 'Second' and 'Third' at a later time?
Update
Based on some of the feedback, I have added some content to try and clear up what I would like to do. Something similar to this effect.
I know from just looking at the code below that, it is a waste of performance "if" it did work and because it doesn't, know I am approaching the problem from the wrong angle.The end objective isn't all to uncommon at a guess from some of the frameworks I've used.
I'm more trying to base this particular bit of code on the CodeIgniter approach where you can define (what below) is STR_CLASS_NAME in a config file and then at any point through the operation of the program, use it as i have dictated.
STR_CLASS_NAME = 'Second';
class First {
protected $intTestOne = 100;
public function __construct() {
$strClassName = STR_CLASS_NAME;
return new $strClassName();
}
public function TestOne() {
echo $this->intTestOne;
}
protected function TestThreePart() {
return '*Drum ';
}
}
class Second extends First{
/* Override value to know it's working */
protected $intTestOne = 200;
/* Overriding construct to avoid infinite loop */
public function __construct() {}
public function TestTwo() {
echo 'Using method from extended class';
}
public function TestThree() {
echo $this->TestThreePart().'roll*';
}
}
$Test = new First();
$Test->TestOne(); <-- Should echo 200.
$Test->TestTwo(); <-- Should echo 'Using method from extended class'
$Test->TestThree(); <-- Should echo '*Drum roll*'
You may be asking, why do this and not just instantiate Second, well, there are cases when it is slightly different:
STR_CLASS_NAME = 'Third';
class Third extends First{
/* Override value to know it's working */
protected $intTestOne = 300;
/* Overriding construct to avoid infinite loop */
public function __construct() {}
public function TestTwo() {
echo 'Using method from extended class';
}
public function TestThree() {
echo $this->TestThreePart().'snare*';
}
}
$Test = new First();
$Test->TestOne(); <-- Should echo 300.
$Test->TestTwo(); <-- Should echo 'Using method from extended class'
$Test->TestThree(); <-- Should echo '*Drum snare*'
Situation
I have a an abstract class which extends a base class with the actually implementation; in this case a basic DB wrapper.
class DBConnector ()
class DBConnectorMySQLi extends DBConnector()
As you can see, MySQLi is the implementation. Now, dependant upon a value in the configuration process, a constant becomes the class name I wish to use which in this case (as shown below builds DBConnectorMySQLi.
define('STR_DB_INTERFACE', 'MySQLi');
define('DB_CLASS', 'DBConnector'.STR_DB_INTERFACE);
Objective
To have a base class that can be extended to include the implementation
For the code itself not to need know what the name of the implementation actually is
To (in this case) be able to type or use a project accepted common variable to create DBConnectorMySQLi. I.E. $db or something similar. W
Issue
When it comes to actually calling this class, I would like the code to be shown as below. I was wondering whether this is at all possible without the need to add any extra syntax. On a side note, this constant is 100% guaranteed to be defined.
$DBI = new DB_CLASS();
Solution 1
I know it is possible to use a reflection class ( as discussed in THIS QUESTION) and this works via:
$DBI = new ReflectionClass(DB_CLASS);
However, this creates code that is "dirtier" than intended
Solution 2
Start the specific implementation of DBConnectorMySQLi within the constructor function of DBConnector.
define('STR_DB_INTERFACE', 'MySQLi');
define('DB_CLASS', 'DBConnector'.STR_DB_INTERFACE);
class DBConnector() { public function __construct() { $this->objInterface = new DBConnectorMySQLi(); }
class DBConnectorMySQLi()
This however would result in the need to keep on "pushing" variables from one to the other
Any advice is much appreciate
You can use variables when you instantiate a class.
$classname = DB_CLASS;
$DBI = new $classname();
Source: instantiate a class from a variable in PHP?
I am developing a CRM for our agency in Codeigniter and I have a question that I can't seem to find a solid answer on. If I have a task that I do on the majority of methods in a controller, is there any way to define that action only once? For instance...
Every view call gets passed the $data variable, like so...
$this->load->view('templates/template.php', $data);
So if I am doing something like getting the admins information in every function of the controller, how can i tell it to do that action ONE time and pass it to all my functions.
Like this...
$data['admin'] = $this->Crm_model->get_admin();
I've tried putting that ^ in the constructor and it doesn't work. Any ideas?
If you do:
$data['admin'] = $this->Crm_model->get_admin();
in the constructor, $data's scope is limited to the constructor. You need to create it as a class property so it is scoped to the entire class. Do this instead
$this->data['admin'] = $this->Crm_model->get_admin();
in the constructor, and then in other methods, you can access the array by doing $this->data
Here's an example:
class Foobar extends CI_Controller {
public function __construct() {
$this->data['foo'] = "bar";
}
public function index() {
// use the class property data here to add more info to it
$this->data['hello'] = "world";
// now pass this to the view
$this->load->view('myView', $this->data);
// myView will receive both $foo and $hello
}
}
Im making a class in php, but Im having some problems with one of the class variables. I declare a private variable, then in the constructor set it. However, later in the class I have a method that uses that variable. The variable in this case is an array. However, the method says the array is blank, but when I check it in the constructor, it all works out fine. So really the question is, why does my array clear, or seem to clear, after the constructor?
<?php
class Module extends RestModule {
private $game;
private $gamearray;
public function __construct() {
require_once (LIB_DIR."arrays/gamearray.php");
$this->gamearray = $gamesarray;
$this->game = new Game();
$this->logger = Logger::getLogger(__CLASS__);
$this->registerMethod('add', array(Rest::AUTH_PUBLIC, Rest::AUTH_USER, Rest::AUTH_ADMIN), true);
$this->registerMethod('formSelect', array(Rest::AUTH_PUBLIC, Rest::AUTH_USER, Rest::AUTH_ADMIN), false);
}
public function add(){
$game = Utility::post('game');
}
public function formSelect(){
$gamename = Utility::get('game');
$this->$gamearray[$gamename];
}
}
The array is pulled in from another file because the array contains a lot of text. Didn't want to mash up this file with a huge array declared in the constructor. The scrolling would be tremendous. Any explanation would be nice, I like to understand my problems, not just fix em'.
You have a typo:
public function formSelect(){
$gamename = Utility::get('game');
$this->gamearray[$gamename]; // Remove the $ before gamearray
}
Moreover, in your case, include is better than require_once.
If you want to go deeper, you could rewrite $gamearray assignment like this:
// Module.php
$this->gamearray = include LIB_DIR.'arrays/gamearray.php';
// gamearray.php
return array(
// Your data here
);
I am trying to initialize data in index function of controller, so that initialized data can be used in subsequent functions of controller. But the problem is data is not being displayed when I am trying to access it from other function. All of this is just to follow a sort of object oriented pattern.
Here is my code.
class Dashboard extends CI_Controller
{
private $account_data; /*Declaration*/
private $profile_data;
function __construct() {
// code...
}
function index() /*Here I am initializing data*/
{
$this->load->model('db_model');
$this->account_data = $this->db_model->get_row();
$this->profile_data = $this->db_model->get_row();
$this->load->view('user/dashboard');
}
function function account_details()
{
print_r($this->account_data); // This displays nothing
}
/*other function...*/
}
Idea is to get data once and use it for other functions and if data is updated again calls a function to initialize it.
But it is not working out. Please help me. Also suggest if I am following right approach.
Thanks for your time.
index method is not initializer, its default page/sub_method,
if you call the "*account_details*" in url as index.php/dashboard/account_details the index wont be called.
try put the code on constructor,
class Dashboard extends CI_Controller
{
private $account_data; /*Declaration*/
private $profile_data;
function __construct() { /*Here I am initializing data*/
parent::CI_Controller(); // Thank you Sven
$this->load->model('db_model');
$this->account_data = $this->db_model->get_row();
$this->profile_data = $this->db_model->get_row();
}
function index()
{
$this->load->view('user/dashboard');
}
function function account_details()
{
print_r($this->account_data); // This displays nothing
}
/*other function...*/
}
Note : don't the models or other computations on __construct() if you don't need on all methods of this controller.
create a private method like "model_initializer()" put this codes on this scope, and the call it in your other methos as $this->model_initialize(); if you need.
Thanks yo Sesama Sesame for note,
I have a PHP web application built with CodeIgniter MVC framework. I wish to test various controller classes. I'm using Toast for unit testing. My controllers have no state, everything they process is either saved into session or passed to view to display. Creating a mock session object and testing whether that works properly is straightforward (just create a mock object and inject it with $controller->session = $mock).
What I don't know, is how to work with views. In CodeIgniter, views are loaded as:
$this->load->view($view_name, $vars, $return);
Since I don't want to alter CI code, I though I could create a mock Loader and replace the original. And here lies the problem, I cannot find a way to derive a new class from CI_Loader.
If I don't include the system/libraries/Loader.php file, the class CI_Loader is undefined and I cannot inherit from it:
class Loader_mock extends CI_Loader
If I do include the file (using require_once), I get the error:
Cannot redeclare class CI_Loader
Looks like CI code itself does not use require_once from whatever reason.
Does anyone here have experience with unit testing CodeIgniter powered applications?
Edit: I tried to inject a real loader object at run-time into a mock class, and redirect all calls and variables with __call, __set, __get, __isset and __unset. But, it does not seem to work (I don't get any errors though, just no output, i.e. blank page from Toast). Here's the code:
class Loader_mock
{
public $real_loader;
public $varijable = array();
public function Loader_mock($real)
{
$this->real_loader = $real;
}
public function __call($name, $arguments)
{
return $this->real_loader->$name($arguments);
}
public function __set($name, $value)
{
return $this->real_loader->$name = $value;
}
public function __isset($name)
{
return isset($this->real_loader->$name);
}
public function __unset($name)
{
unset($this->loader->$name);
}
public function __get($name)
{
return $this->real_loader->$name;
}
public function view($view, $vars = array(), $return = FALSE)
{
$varijable = $vars;
}
}
Alternatively, you could do this:
$CI =& get_instance();
$CI = load_class('Loader');
class MockLoader extends CI_Loader
{
function __construct()
{
parent::__construct();
}
}
Then in your controller do $this->load = new MockLoader().
My current solution is to alter the CodeIgniter code to use require_once instead of require. Here's the patch I'm going to send to CI developers in case someone needs to do the same until they accept it:
diff --git a/system/codeigniter/Common.php b/system/codeigniter/Common.php
--- a/system/codeigniter/Common.php
+++ b/system/codeigniter/Common.php
## -100,20 +100,20 ## function &load_class($class, $instantiate = TRUE)
// folder we'll load the native class from the system/libraries folder.
if (file_exists(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT))
{
- require(BASEPATH.'libraries/'.$class.EXT);
- require(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
$is_subclass = TRUE;
}
else
{
if (file_exists(APPPATH.'libraries/'.$class.EXT))
{
- require(APPPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
else
{
- require(BASEPATH.'libraries/'.$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
}
I can't help you much with the testing, but I can help you extend the CI library.
You can create your own MY_Loader class inside /application/libraries/MY_Loader.php.
<?php
class MY_Loader extends CI_Loader {
function view($view, $vars = array(), $return = FALSE) {
echo 'My custom code goes here';
}
}
CodeIgniter will see this automatically. Just put in the functions you want to replace in the original library. Everything else will use the original.
For more info check out the CI manual page for creating core system classes.
I'm impressed by the code you are trying to use.
So now I'm wondering how the 'Hooks' class of CodeIgniter could be of any help to your problem?
http://codeigniter.com/user_guide/general/hooks.html
Kind regards,
Rein Groot
The controller should not contain domain logic, so unit tests make no sense here.
Instead I would test the controllers and views with acceptance tests.