EllisLab text mark
Advanced Search
     
Extending the Loader class (more or less)
Posted: 07 October 2007 11:15 AM
Joined: 2006-10-25
2 posts

Hello

Problem:
I need to implement some sort of themes for a site I’m working on.


Solution:
Extend the CI_Loader class and overwrite the “view” function, to add an optional subdirectory for my views. Something like:

application/themes/mytheme/view.php 

Extending the CI_Loader class would be simple, but we have a problem: the CI_Controller class extends CI_Loader and somewhere in the code we have:

$this->load =& $this//to allow $this->load->view(...) to work 

So, $this->load isn’t an instance of the CI_Loader class (that we might have overwritten), but it’s a reference to the controller object.

The solution is simple: between CI_Controller and our app controllers, we will add a new Controller class, that will overwrite the “view” function. The result will look like:
CI_Loader -> CI_Controller -> MyBaseController -> App Controller.

<?php
// application/controllers/MyControllerBase.php
class MyControllerBase extends Controller {
    
var $theme NULL;
    function 
view($view$vars = array(), $return FALSE$fixored FALSE)
    
{
    $folder 
'';
    if (!
is_null($this->theme)) {
        $folder 
'themes/'.$this->theme.'/';
    
}
    
return $this->_ci_load(array('view' => $folder.$view'vars' => $this->_ci_object_to_array($vars), 'return' => $return));
    
}
}
?> 

and an application controller:

<?php
require_once(APPPATH.'/controllers/MyControllerBase.php');
class 
SomeController extends MyControllerBase {
    
...
}
?> 


I hope this helps and of course, if you have better suggestions, please post them here.

Peter

 Signature 

If one beer won’t fix it, then two of them definitely will.

 
Posted: 07 October 2007 11:49 AM   [ # 1 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-23
3194 posts

I haven’t fully digested what you’re writing here, but I wanted to point out that there is a “display_override” hook available (hooks docs) that might be more flexible for you, and wouldn’t require any core hacking.

 Signature 

DerekAllard.com - CodeIgniter, ExpressionEngine, and the World of Web Design

 
Posted: 07 October 2007 12:09 PM   [ # 2 ]   [ Rating: 0 ]
Joined: 2006-10-25
2 posts

The display_override hook will give me access to a string that will contain all the generated output (right? :D). I find it a bit harder to process that string and change the layout, than just use another view for a different layout smile

Let’s consider the following directory structure:

application/view/ <—will contain the “default” views.
application/view/themes/ <—will contain a subfolder for each theme.
application/view/themes/myfancytheme/ <—will contain all views that are different from the default one.


If I don’t set a theme, then the “login.php” view will be loaded from the application/view/ directory. If I set the theme to “myfancytheme”, the the “login.php” view will be loaded from the application/view/themes/myfancytheme/ directory.

Ofcourse, the code I posted earlier is just the rough idea, still needs checks (if the theme directory exists and so on), maybe a fallback mecanism (that will allow me to rewrite only some views) etc.

 Signature 

If one beer won’t fix it, then two of them definitely will.

 
Posted: 07 October 2007 02:14 PM   [ # 3 ]   [ Rating: 0 ]
Avatar
Joined: 2007-02-06
743 posts

The only problem with your code is that you are calling private CI core methods that aren’t part of the public API (_ci_load() and _ci_object_to_array()). A couple of alternatives:

#1.
<?php
// application/controllers/MyControllerBase.php
class MyControllerBase extends Controller {
    
var $theme NULL;
    function 
loadview($view$vars = array(), $return FALSE)
    
{
       $folder 
'';
       if (!
is_null($this->theme)) {
           $folder 
'themes/'.$this->theme.'/';
       
}
       
       
if ($return
           
return $this->load->view($folder.$view$varstrue);
       
}
       
else {
           $this
->load->view($folder.$view$vars);
       
}
    }
}
?> 

#2.
// or more simply, add a method that appends the theme folder to the view
$view $this->getViewPath('someView');
$this->load->view($view$vars); // or $this->load->view($view, $vars, true); 
 Signature 

“I am the terror that flaps in the night”

 
Posted: 13 December 2007 05:32 PM   [ # 4 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-06
6 posts

Hey y’all…

Back with hopefully another solid Giveback to the community. I like having a “layout” for those of you familiar with Ruby On Rails, for my sites. Figured this is what the original posted was going for. I’d ultimatly love it if this could be turned into a plug-in of sorts, or some other “override” which doesn’t actually “hack” the core of CI, so to all

BEWARE: THIS WILL MODIFY YOUR CORE SYSTEM.

Not for the faint of heart. That being said, its not a very big change, and I’ve taken the liberty of creating a .patch file for you all. This will only work for unix/linux systems. Run this command to patch the Loader.php file. patch -R path/to/system/libraries/Loader.php < Loader.patch

I recommend you look over the patch file so you know what’s going on. It should be fairly straight forward, and I commented where necessary to blend in with the rest of the file. If you don’t have Unix/Linux, and haven’t made any changes to Loader.php, you can simply replace it with the one I’ve attached. I recommend the patch if possible over replacing. This also allows for _partial.php views. You can call them from within your views and layouts.

After you’re patched up, make a sibling directory to views called layouts and pop in your php/html layout there. Then just define your layout in your controller by saying: $layout = “name_of_layout”;

class Admin extends Controller {

    
var $layout "home";

    function 
Admin()
    
{
        parent
::Controller();
    
}

In your layout file, you’ll want something like this. The important piece is the bit of PHP imbedded in there:

<div id="primary">
    
<?$content_for_layout ?>
    
<span class="clear"/>
</
div

EDIT: Turns out I can’t attach a patch file. I’ll just post it here. One caveat to be aware of. Files have different line endings between computer systems. I work in the UNIX standard LF. Just make sure both the patch and the Loader.php file have the same encoding and line endings or the patch will fail. Save the following code to a file on your computer.

34d33
<     var $_ci_layout_path    '';
57d55
<         $this->_ci_layout_path APPPATH.'layouts/';
266,289d263
<      * Load Partial
<      *
<      * 
This function is used to load a "partial" file.  It has three parameters:
<      *
<      * 
1. The name of the "partial" file to be included.
<      * 
2. An associative array of data to be extracted for use in the view.
<      * 
3. TRUE/FALSE whether to return the data or load it.  In
<      * some cases it's advantageous to be able to return data so that
<      * a developer can process it in some way.
<      *
<      * @access    public
<      * @param    string
<      * @param    array
<      * @param    bool
<      * @return    void
<      */
<     function partial($view, $vars = array(), $return = FALSE)
<     {
<         return $this->_ci_load(array('
partial' => true, 'view' => $view, 'vars' => $this->_ci_object_to_array($vars), 'return' => $return));
<     }
<     
<     // --------------------------------------------------------------------
<     
<     /**
600c574
<         foreach (array('
partial', 'view', 'vars', 'path', 'return') as $val)
---
>         foreach (array('
view', 'vars', 'path', 'return') as $val)
604,608c578
<         
< //        if ($partial && $view[0] != '
_') {
< //            $view = substr_replace($view, "/_".$view, strripos($view, "/"));
< //        }
<             
---

675,677c645
<         eval('
?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($path))).'<?php ');
<         
<         if (!$partial)
---
>         if ((bool) @ini_get('
short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE)
679,695c647,651
<             if (!isset($this->layout)) {
<                 show_error('
No layout defined in one of your controllers');
<             }
<         
<             $layout_ext = pathinfo($this->layout, PATHINFO_EXTENSION);
<             $layout_file = ($layout_ext == '') ? $this->layout.EXT : $this->layout;
<             $layout_path = $this->_ci_layout_path.$layout_file;
<             
<             if ( ! file_exists($layout_path))
<             {
<                 show_error('
Unable to load the requested layout'.$this->_ci_layout_path.$layout_file);
<             }
<             
<             $content_for_layout = ob_get_contents();
<             @ob_end_clean();
<         
<             include $layout_path;
---
>             echo eval('
?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($path))).'<?php ');
>         }
>         else
>         {
>             include($path); 

Hope this helps!

Cheers,

—volte