Loading Classes on the fly

With the LazyLoading Model from Lorenzo (there are other sources as well) we already speed up the application by 20-40% (depending on the application size and amount of models).

Something i never really liked is that components and helpers can only be added to controllers globally in $components and $helpers. Unlike behaviors (attach/detach).
What if we need some of them only in a single action? Seems to be overhead to include it in every action of the controller.
I used to patch around that problem with App::import() and all kinds of init stuff in the action itself.

Now I wrote this (and suddenly my code is so much cleaner):

class CommonComponent extends Object {

    /**
     * for automatic startup
     * for this helper the controller has to be passed as reference
     * 2009-12-19 ms
     */
    function initialize($Controller) {
        $this->Controller = $Controller;
    }
    
    /**
     * add helper just in time (inside actions - only when needed)
     * aware of plugins
     * @param mixed $helpers (single string or multiple array)
     * 2010-10-06 ms
     */
    function loadHelper($helpers = array()) {
        $this->Controller->helpers = array_merge($this->Controller->helpers, (array)$helpers);
    }
    
    /**
     * add lib just in time (inside actions - only when needed)
     * aware of plugins and config array (if passed)
     * ONLY works if constructor consists only of one param!
     * @param mixed $libs (single string or multiple array)
     * 2010-11-10 ms
     */
    function loadLib($libs = array()) {
        foreach ((array)$libs as $lib => $config) {
            if (is_int($lib)) {
                $lib = $config;
                $config = null;
            }
    
            list($plugin, $libName) = pluginSplit($lib);
            if (isset($this->Controller->{$libName})) {
                continue;
            }
            App::import('Lib', $lib);
            $lib = new $libName($config);
            $this->Controller->{$libName} = $lib;
        }
    }
    
    /**
     * add component just in time (inside actions - only when needed)
     * aware of plugins and config array (if passed)
     * @param mixed $helpers (single string or multiple array)
     * 2010-11-08 ms
     */
    function loadComponent($components = array()) {
    
        foreach ((array)$components as $component => $config) {
            if (is_int($component)) {
                $component = $config;
                $config = null;
            }
            list($plugin, $componentName) = pluginSplit($component);
            if (isset($this->Controller->{$componentName})) {
                continue;
            }
            App::import('Component', $component);
    
            $componentFullName = $componentName.'Component';
            $component = new $componentFullName($config);
    
            if (method_exists($component, 'initialize')) {
                $component->initialize($this->Controller);
            }
            if (method_exists($component, 'startup')) {
                $component->startup($this->Controller);
            }
            $this->Controller->{$componentName} = $component;
        }
    }

}

Now the following is possible (inside controller actions):

$this->Common->loadComponent('RequestHandler');
$this->Common->loadComponent('Tools.Currency');
$this->Common->loadComponent(array('RequestHandler', 'Test'=>array('x'=>'y'))); # passing options

$this->Common->loadLib('Tools.RandomLib');
$this->Common->loadLib(array('Tools.RandomLib', 'MarkupLib'));
$this->Common->loadLib(array('Tools.RandomLib', 'MarkupLib'=>array('x'=>'y'))); # passing options

$this->Common->loadHelper('Text');
$this->Common->loadHelper(array('Text', 'Tools.Datetime'));

etc.

Maybe without "Common->" or any other helping component some time in the future 🙂

I hope the enhancement ticket will soon be patched into the core. Feel free to vote it up:
cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/1277

4 Comments

  1. Works well but I seem to have found a glitch. By bypassing the usual Component->init(), it seems that sub-components are not loaded.

    So if in my Test component I have:

    var $components = array('Session");

    the Session component won’t be loaded for Test.

    I narrowed this down to
    Controller->contructClasses();
    and
    Component->init()

    They seem to be very intertwined so it’s complex to run ->init() by itself from within another component.

    Any ideas?

  2. Well, after some testing, I seem to have figured it out:

    function loadComponent($components = array()) {
     
            foreach ((array)$components as $component => $config) {
                if (is_int($component)) {
                    $component = $config;
                    $config = null;
                }
                list($plugin, $componentName) = pluginSplit($component);
                if (isset($this->Controller->{$componentName})) {
                    continue;
                }
                App::import('Component', $component);
    
                $componentFullName = $componentName.'Component';
                $component = new $componentFullName();
     
                $ComponentObj =& new Component();
                $ComponentObj->__controllerVars = array(
                    'plugin' => $this->Controller->plugin,
                    'name' => $this->Controller->name,
                    'base' => $this->Controller->base
                );
                $ComponentObj->_loadComponents($component);
     
     
                if (method_exists($component, 'initialize')) {
                    $component->initialize($this->Controller,$config);
                }
                if (method_exists($component, 'startup')) {
                    $component->startup($this->Controller);
                }
                $this->Controller->{$componentName} = $component;
            }
        }

    I also moved the $config to the component->initialize() call instead of the component constructor call.

  3. nice work. i will try it out.
    with cake2.0 most of that stuff is available in the core anyway.
    but for 1.3 we still need those hacks 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.