First: I've already reviewed all the questions I can find about __destruct() and CodeIgniter, and none seem to address the same issue I'm seeing.
Right. That aside. I'll show code first, as the problem will make more sense after reading. (N.B.: some code redacted, but nothing important to the question. The Billing::index function however, is comprises only one space character in production as well as here)
application/core/MY_Controller.php
class MY_Controller extends CI_Controller{
public $view = '';
public $data = array();
public $template = '';
function __destruct(){
if(!is_null($this->template) && ($this->template == '')){
$this->template = 'public';
}
if($this->view == ''){
$this->view = $this->uri->segment(1,'index').'/'.$this->uri->segment(2,'index');
}
if(!is_null($this->template)){
echo $this->load->view('templates/'.$this->template.'/top',$this->data,true);
}
echo $this->load->view('views/'.$this->view,$this->data,true);
if(!is_null($this->template)){
echo $this->load->view('templates/'.$this->template.'/bottom',$this->data,true);
}
}
}
class MY_ProtectedController extends MY_Controller{
function __destruct(){
parent::__destruct();
}
}
application/controllers/billing.php
class Billing extends MY_ProtectedController{
public function index(){ }
}
This, loads perfectly. The main "billing" page is just HTML, so the destructor fires the right template and view.
However, I would love to remove the redundant public function index(){ }, as it really does nothing.
So if I remove that from application/controllers/billing.php so that it's just
class Billing extends MY_ProtectedController{ }
and visit /billing, I get the output I want, however, I get it twice.
If I do some test echos, MY_ProtectedController::__destruct() is called once, but MY_Controller::__destruct is called twice.
My question is: why is this the case, and can it be stopped?
I'm not super familiar with CI core, and I figure that by the time I dig far enough to find the controller instantiation, someone here has probably already got an answer. Will of course update this if my own digging turns anything up.
Solution ended up being fairly simple - but credit for spotting this goes to one of our other programmers.
If I put some code in my 404 route to prevent the auto-destructor from running, I only get one set of output.
class Index extends MY_PublicController {
public function not_found(){
$this->cancel_destruct = TRUE;
}
}
class MY_Controller extends CI_Controller{
$this->cancel_destruct = FALSE;
function __destruct(){
if(!$this->cancel_destruct){
[...]
}
}
}
Related
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!
I want to call a static method from a variabe class in PHP. As pointed out several times on SO and because it is general practice, the following works as expected:
class Foo {
public function compile($strClass) {
$strClass::find(); // this works
}
}
Nonetheless I have to call different find methods from $strClass from different methods of a class Foo. That is, why I want to store $strClass in $this->strClass. Unfortunately, this doesn't work:
class Foo {
protected $strClass;
public function __construct($strClass)
{
$this->strClass = $strClass;
}
public function compile($strClass) {
$this->strClass::find(); // this does not work
}
}
Any idea or hint on how to solve that issue?
Update:
As pointed out in the comments, it might be a solution to use call_user_func like this:
call_user_func(array($this->strClass, 'find'), $strParam);
Anyhow, this makes code completion in PHPstorm impossible. Any hints on that? Maybe using code annotation?
You can change your compile method to this:
public function compile($strClass) {
call_user_func(array($this->strClass, 'find'));
}
This class design is flawed. I would try to get rid of the static methods completely, but here is a solution that exploits the fact that you can call static methods on objects:
class Foo {
protected $strClass;
public function __construct($strClass)
{
$this->strClass = new $strClass;
}
public function compile($strClass) {
$this->strClass::find();
}
}
UPDATE: nevermind, this is a syntax error in all current PHP versions, you actually have to do it like this:
$strClass = $this->strClass;
$strClass::find();
And this works with your original code as well, where $this->strClass is a string.
strange problem here. Using Yii framework I have the following class
class HtmlTableUi
{
public function __construct(CWebModule &$module,$data,$actions)
{
//...code goes here...
}
protected function renderTable()
{
//... code goes here ...
}
}
I can call HtmlTableUi::renderTable() from instance of HtmlTableUi in SchedulerModule class which is the main class in a separate application module. My SchedulerModule.php file:
<?php
Yii::import('scheduler.components.HtmlTableUi');
class SchedulerModule extends CWebModule
{
public function init()
{
parent::init();
}
public function beforeControllerAction($controller, $action)
{
return true;
}
public function printUI($data,$actions,$submitPath)
{
$ui = new HtmlTableUi($this,$data,$actions,$submitPath);
$ui->renderTable();
}
}
Here comes the tricky part - when I call SchudulerModule::printUI from view (index.php) this way
<?php
$this->module->printUI($casino,$actions,null);
?>
the code flow goes through SchudulerModule::printUI until reaching the point where renderTable is invoked ($ui->renderTable();) and instead stepping at the first line inside the body of that method it, contrary to any logic and rules, surprisingly jumps to CHttpSession::close !!?
Notice the Call Stack before invoking renderTable
protected/modules/scheduler/SchedulerModule.php.SchedulerModule->printUI:153
protected/modules/scheduler/views/default/index.php.require:21
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CBaseController.php.CBaseController->renderInternal:127
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CBaseController.php.CBaseController->renderFile:96
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CController.php.CController->renderPartial:870
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CController.php.CController->render:783
protected/modules/scheduler/controllers/DefaultController.php.DefaultController->actionIndex:57
and after:
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CHttpSession.php.CHttpSession->close:134
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CHttpSession.php.SchedulerModule->printUI:0
protected/modules/scheduler/views/default/index.php.require:21
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CBaseController.php.CBaseController->renderInternal:127
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CBaseController.php.CBaseController->renderFile:96
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CController.php.CController->renderPartial:870
/home/default/workspace/src/vlt/web/yii-1.1.12.b600af/framework/web/CController.php.CController->render:783
protected/modules/scheduler/controllers/DefaultController.php.DefaultController->actionIndex:57
Has anyone had similar issue? Can anyone explain that behavior?
I found the solution!!! The access modifier of renderTable is supposed to be public, not protected. This wrong somehow in Yii framework results in unexpected runtime behavior instead of compilation error.
I have the following snippet of code.
abstract class MrParent {
public function __construct() {
$this->var = 'a';
}
}
class MrChild extends MrParent {
public function hello() {
echo 'Hello';
}
}
$MrGuy = new MrChild();
Now, in PhpStorm, when I middle-click ("Go To Declaration") on the last line of the "MrChild" class, the cursor jumps up to the "__construct" line. I was expecting it to go to the "class MrChild extends MrParent" line.
In a single document, this is OK, but in a setup where it's one class per file, this is quite annoying because it means the IDE is constantly showing me the class I don't want.
I know that if I added the following code to the "MrChild" class, I'd get what I want, but that seems like I shouldn't be fixing what I consider to be an IDE bug by adding extra code.
public function __construct() {
parent::__construct();
}
Do you have any suggestion?
You are facing WI-4880 issue. Feel free to watch/vote.
I am working on creating my own very simple MVC and I am brainstorming ways to go from the controller to the view. Which involves sending variables from a class to just a plain old PHP page.
I am sure that this has been covered before, but I wanted to see what kind of ideas people could come up with.
//this file would be /controller/my_controller.php
class My_Controller{
function someFunction(){
$var = 'Hello World';
//how do we get var to our view file in the document root?
//cool_view.php
}
}
Some kind of hashtable is a good way to do that. Return your variables as association array which will fill all the gaps in your view.
Store your variables as a property in your controller object, then extract them when rendering
class My_Controller {
protected $locals = array();
function index() {
$this->locals['var'] = 'Hello World';
}
protected function render() {
ob_start();
extract($this->locals);
include 'YOUR_VIEW_FILE.php';
return ob_get_clean();
}
}
You can define those magic __get and __set methods to make it prettier
$this->var = 'test';
I'm also developing my own simple MVC and the most simple way to do it is ...
class My_Controller
{
function someFunction() {
$view_vars['name'] = 'John';
$view = new View('template_filename.php', $view_vars);
}
}
View class
class View
{
public function __construct($template, $vars) {
include($template);
}
}
template_filename.php
Hello, <?php echo $vars['name'];?>
I highly recommend you to take a look at PHP Savant http://phpsavant.com/docs/
I'd checkout Zend_View and how it accomplished view rendering.
You can get the source of View and AbstractView on github - unfortunaly I don't find the current repository (in svn) that easy to browse.
Essentially the view variables are contained in a View object (which your controller would have access to), then the template (plain old php document) is rendered inside that object. That method allows the template access to $this.
It would be something like:
<?php
class View
{
public function render()
{
ob_start();
include($this->_viewTemplate); //the included file can now access $this
return ob_get_clean();
}
}
?>
So in your controller:
<?php
class Controller
{
public function someAction()
{
$this->view->something = 'somevalue';
}
}
?>
And your template:
<p><?php echo $this->something;?></p>
In my opinion this pattern allows you much flexibility with the view.
I created my own MVC for the free PHP course I'm conducting for a handful of people wanting to get better at PHP.
By far the best way to do this is to use the Command + Factory pattern.
E.g.
interface ControllerCommand
{
public function execute($action);
}
In each controller:
class UserController implements ControllerCommand
{
public function execute($action)
{
if ($action == 'login')
{
$data['view_file'] = 'views/home.tpl.php';
}
else if ($action == 'edit_profile')
{
$data['view_file'] = 'views/profile.tpl.php';
$data['registration_status'] = $this->editProfile();
}
return $data;
}
}
From your main front controller:
$data = ControllerCommandFactory::execute($action);
if (!is_null($data)) { extract($data); }
/* We know the view_file is safe, since we explicitly set it above. */
require $view_file;
The point is that every Controllercommand class has an execute function and that returns its view and any data for that view.
For the complete MVC, you can access the open source app by emailing me at theodore[at]phpexperts.pro.