Page 1 of 1

application controller and application initializer

Posted: Wed Sep 19, 2007 6:30 pm
by Luke
I'm writing an application right now with the Zend Framework. I'm having a very difficult time deciding what should go in my application initialization class (basically the bootstrap) and what should go in my application controller (extends Zend_Controller_Action and is extended by all of my controllers within the app). For instance, the session. Should it start in the bootstrap? How about the database initialization? My bootstrap file looks like this...

Code: Select all

<?php
require_once 'app/MC2/Bag.php';
MC2_Bag::run(__FILE__);
MC2_Bag basically just contains all of the initialization that normally would happen in index.php. I don't really like having all of that stuff just out in the index file. I don't really know my reasoning for that. app/MC2/Bag.php looks like this:

Code: Select all

<?php
/**
 * Disable this in production environments
 */
error_reporting(E_ALL^E_NOTICE);
ini_set('display_errors', 'on');

define('DS', DIRECTORY_SEPARATOR);
define('PS', PATH_SEPARATOR);
define('BP', dirname(dirname(__FILE__)));

/**
 * The library directory should always be directly under this file, but if
 * for some reason it isn't, you'll need to add its location to the path
 */
set_include_path('./library' . PS . './app' . PS . get_include_path());

require_once 'functions.php';
require_once 'Zend/Debug.php';

/**
 * Contains some of the most low-level operations necessary
 * for this application
 */
class MC2_Bag
{

    /**
     * This is the directory in which the application resides
     * not the url, but the directory
     */
    protected static $basedir = null;

    public static function run($bootstrap)
    {
    
        self::setBaseDir( dirname($bootstrap) );
        
        // initialize configuration information
        $config = self::initConfig();
        
        // connect to the database
        $db = self::connect($config->database->toArray());
        
        /*require_once 'Zend/Registry.php';
        $registry = Zend_Registry::getInstance();*/

        $view = self::initView();
        
        /**
         * Front controller pattern
         */
        require_once 'Zend/Controller/Front.php';
        $front = Zend_Controller_Front::getInstance();
        
        $router = self::initRoutes($front);

        /**
         * Inject db connection and other necessities into the front controller
         */
        $front->setParam('db', $db);
        $front->setParam('config', $config);

        /**
         * Now we set up the front controller to accept multiple modules. This is 
         * a modular front controller set up.
         */
        //$front->addControllerDirectory(self::getBaseDir() . DS . 'modules' . DS . 'default', 'default');
        $front->setModuleControllerDirectoryName('')
              ->addModuleDirectory(self::getBaseDir() . DS . 'modules')
              // @todo: find out what this actually does and possibly just enable it when debug is on
              ->throwExceptions( (bool) $config->throwexceptions )
              ->dispatch();
    }
    
    public static function setBaseDir($dir)
    {
        self::$basedir = $dir . DS . 'app';
    }
    
    public static function getBaseDir()
    {
        return self::$basedir;
    }
    
    /**
     * Initialize the configuration information and return it as a Zend_Config
     * object
     *
     * @returns Zend_Config
     */
    public static function initConfig()
    {
        require_once 'Zend/Config.php';
        require_once 'config.php';
        
        $config = new Zend_Config($configArray);
        return $config;
    }
    
    public static function initView()
    {
        /**
         * Prepare custom view
         */
        require_once 'Zend/View.php';
        require_once 'Zend/Controller/Action/HelperBroker.php';
        require_once 'Zend/Controller/Action/Helper/ViewRenderer.php';
        
        $view = new Zend_View;
        // add common directories for all Views; always "add", never "set"
        $view      ->addScriptPath(MC2_Bag::getBaseDir() . DS . 'views/common/_elements')
                   ->addScriptPath(MC2_Bag::getBaseDir() . DS . 'views')
                   ->addHelperPath(MC2_Bag::getBaseDir() . DS . 'views/common/_helpers')
                   ->addFilterPath(MC2_Bag::getBaseDir() . DS . 'views/common/_filters');

        $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer;
        $viewRenderer->setView($view);

        // set the Module specific ScriptPath syntax; The Module name is substituted and add to
        // as an additional View ScriptPath when a Controller is executed
        $viewRenderer->setViewScriptPathSpec(':module/:controller/:action.:suffix');

        Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
        return $view;
    }
    
    public static function initRoutes(Zend_Controller_Front $front)
    {
        $router = $front->getRouter();
        require_once 'routes.php'; // defines $routes
        foreach ($routes as $key => $route)
        {
            $router->addRoute($key, $route);
        }
        return $router;
    }
    
    public static function getBaseUrl()
    {
        return Zend_Controller_Front::getInstance()->getRequest()->getBaseUrl();
    }
    
    // @todo: move this out of here and into the Application controller (like it is on luke's laptop)
    public static function connect(Array $parameters)
    {
        require_once 'Zend/Db.php';
        require_once 'Zend/Db/Table/Abstract.php';

        mysql_connect($parameters['host'], $parameters['username'], $parameters['password']);
        mysql_select_db($parameters['dbname']);
        
        try {
            $db = Zend_Db::factory('Pdo_Mysql', $parameters);
            Zend_Db_Table_Abstract::setDefaultAdapter($db);
        } catch (Zend_Db_Adapter_Exception $e) {
            // @todo: handle this exception
            echo $e;
            // maybe a failed login credential, or perhaps the RDBMS is not running
        } catch (Zend_Exception $e) {
            // @todo: handle this exception
            echo $e;
            // maybe factory() failed to load the specified Adapter class
        }
        return $db;
    }
}
And then I have my application controller which is just a controller that all of my controllers extend. It looks like this:

Code: Select all

<?php
require_once 'Zend/Controller/Action.php';

class MC2_Controller_Action extends Zend_Controller_Action
{
    /**
     * Database connection (retrieved from front injection in bootstrap index.php)
     */
    protected $db = null;
    
    /**
     * Configuration object
     */
    protected $config = null;

    /**
     * By setting these variables, I am making them available to all of my controllers
     * It is not necessary to call parent::init() because nothing is in it
     */
    public function init()
    {
        $front = Zend_Controller_Front::getInstance();
        $this->db = $front->getParam('db');
        $this->config = $front->getParam('config');
        $this->initView();
    }
    
    /**
     * This may not be the correct way to do this. I am not sure exactly
     * what initview does and if I should be initializing my view variables
     * here or not. 
     */
    public function initView()
    {
        parent::initView();
        
        $this->view->MEDIA = MC2_Bag::getBaseUrl() . '/' . $this->config->view->media_url;
        $this->view->BASEURL = MC2_Bag::getBaseUrl();
        $this->view->DEBUG = $this->config->debug;
        $this->view->HEAD = '';
        $this->view->config = $this->config->view;
        
        // if debug is on, let's show the developer their environment 
        if ($this->config->debug)
        {
            $this->view->POST    = $_POST;
            $this->view->GET     = $_GET;
            $this->view->SESSION = $_SESSION;
            $this->view->COOKIE  = $_COOKIE;
        }
    }
    
    // @todo: move this out of the controller... this is view helper logic
    protected function includeJquery()
    {
        $this->view->javascript($this->config->view->jquery);
    }
    
    /**
     * This is a function that is used by form methods to generically process
     * a form. It takes a Zend_Filter_Input instance as its argumnent, checks
     * the input's validity, and if it is invalid, it sets all the necessary
     * view variables so that error messages are populated etc. and returns
     * false. Otherwise, it returns true
     */
    protected function processInput(MC2_Input $input)
    {
        $input->filter();
        if ($input->isValid())
        {
            return $input->getData();
        }
        else
        {
            foreach ($input->getMessages() as $element => $messages)
            {
                foreach ($messages as $message)
                {
                    $this->view->formSetError($element, $message);
                }
            }
        }
        return false;
    }
}
?>
It just seems that the two do a lot of the same kind of stuff. Should I be initializing everything in the bootstrap class and then passing it in via $front->setParam()?

Posted: Thu Sep 20, 2007 4:35 am
by Maugrim_The_Reaper
MC2_Bag basically just contains all of the initialization that normally would happen in index.php. I don't really like having all of that stuff just out in the index file. I don't really know my reasoning for that. app/MC2/Bag.php looks like this:
Putting stuff in index.php rarely made sense - I just include a bootstrap.php file from the main code location off the web root.

Generally I do as much as possible in the bootstrap, use $front->setParam(), and use the base controller primarily for stamping any specific needs which aren't really configurable. From one point of view - the bootstrap is executed once, but a base controller class could be instantiated multiple times in the application flow. So if you only need to do something once - might as well do it in the bootstrap - not a multiply instantiated controller class.

Of course using multiple controllers all the time is probably avoidable.

Posted: Thu Sep 20, 2007 12:46 pm
by John Cartwright
Maugrim, when you mention several controllers should be extending action controller during the programs flow, are you refering the $this->_forward() or something I am unaware of? Nearly always, I end up with only a single class extending the action controller.

Thanks :)

Posted: Thu Sep 20, 2007 6:05 pm
by Kieran Huggins
I have to say, I do love me some PHP, but my first thought when I looked at that code was "eek... RoRttr!"

@NSG: You would LOVE rails. I encourage you to check it out for projects like this.

Posted: Fri Sep 21, 2007 2:29 am
by Luke
I am definitely interested. I'm going to finish learning python / django first, but ruby / rails is next on the list. You've never steered me wrong, and I've heard nothing but good things about rails really. Plus I just got a dedicated server so I can pretty much install whatever I want. :)

Posted: Fri Sep 21, 2007 3:09 am
by Maugrim_The_Reaper
Maugrim, when you mention several controllers should be extending action controller during the programs flow, are you refering the $this->_forward() or something I am unaware of? Nearly always, I end up with only a single class extending the action controller.
Exactly. Not only _forward() though. In ZF 1.1 there will be a new component called Zend_Layout which relies on defining a template for an overarching Layout of a webpage. Dynamic areas would be filled in by dispatching a request to a Controller for each dynamically filled placement block - so say you have the main content, some sidebar widgets, and other stuff. That could add up to a few controller's being instantiated, each extending the App level subclass, and each performing any setup steps you put there.

In case anyone else thinks this is insane - it wasn't my idea :). It's pretty much the only significant departure from my Zend_View Enhanced proposal being adopted in ZF 1.1.
@NSG: You would LOVE rails. I encourage you to check it out for projects like this.
Off-topic: Learn RSpec when learning Ruby and Rails. After I started using it, I found it sorely missed in PHP. It's a Behavior-Driven Development tool which is basically TDD but with a re-focused language that shifts away from testing and towards specifications (i.e. avoids the notion that TDD is a testing methodology). I'm so impressed with it at this stage I'm seriously looking for a PHP variant.

Posted: Fri Sep 21, 2007 10:50 am
by John Cartwright
Maugrim_The_Reaper wrote:
Maugrim, when you mention several controllers should be extending action controller during the programs flow, are you refering the $this->_forward() or something I am unaware of? Nearly always, I end up with only a single class extending the action controller.
Exactly. Not only _forward() though. In ZF 1.1 there will be a new component called Zend_Layout which relies on defining a template for an overarching Layout of a webpage. Dynamic areas would be filled in by dispatching a request to a Controller for each dynamically filled placement block - so say you have the main content, some sidebar widgets, and other stuff. That could add up to a few controller's being instantiated, each extending the App level subclass, and each performing any setup steps you put there.
Yes I do think it is insane, but I think as long as the action controller isn't responsible for too many initializations and shifted towards the bootstrap this seems like a decent solution. :)

Posted: Fri Sep 21, 2007 2:28 pm
by Christopher
Maugrim_The_Reaper wrote:In case anyone else thinks this is insane - it wasn't my idea :). It's pretty much the only significant departure from my Zend_View Enhanced proposal being adopted in ZF 1.1.
Yes, but we blame you! ;) (I actually blame myself for leaving and not helping you battle Ralphie)

Posted: Fri Sep 21, 2007 3:07 pm
by Luke
In case anyone else thinks this is insane - it wasn't my idea Smile. It's pretty much the only significant departure from my Zend_View Enhanced proposal being adopted in ZF 1.1.
yep sounds pretty insane... :roll: when does the insanity end?? :)

Posted: Sat Sep 22, 2007 5:20 pm
by Maugrim_The_Reaper
(I actually blame myself for leaving and not helping you battle Ralphie)
I knew it wasn't my fault! :wink:
Yes I do think it is insane, but I think as long as the action controller isn't responsible for too many initializations and shifted towards the bootstrap this seems like a decent solution.
Once again tempting the mods to remind me of posting off-topic...

Depends on the controller and the context - as with Ninja's question. If nothing else it should be a poster promotion for the Thin Controller/Fat Model camp. As always, refer to XDebug and your preferred cachegrind analyser.

Posted: Sat Sep 22, 2007 5:38 pm
by John Cartwright
I'm trying to figure out what Thin Controller Fat Model camp actually means (terms seem too amgiguous and/or too specified for google) :(

Do you mind providing a link or two so I can follow up on your comments?

Posted: Sat Sep 22, 2007 8:43 pm
by Maugrim_The_Reaper
Search terms on the conventional "title" it's received:
http://www.google.ie/search?q=skinny+co ... =firefox-a

Chris Hartjes' take

It's a fairly simple idea - just seemed not a lot of people were paying attention to it with the dawn of frameworks. It's not uncommon to find applications which have an ActiveRecord powered Model...and that's it. Controllers are given the task of making the data meaningful; applying business logic. Net result? Fat controllers containing hundreds of lines of code encapsulating business logic. I came across the same misconceived practice a few times when explaining what the View Helper Pattern [J2EE Design Patterns] and how it interacts with Models.

Posted: Sat Sep 22, 2007 8:48 pm
by John Cartwright
Havn't looked into the links you've provided yet, but I will do so tommorow.

Thanks, as usual, Maugrim.

Posted: Sun Sep 23, 2007 3:36 am
by Christopher
I had to laugh the first time I read about "Skinny Controller, Fat Model" in a Rails blog. It is a reaction to fact that Rail's ActiveRecord makes some things easy, but also promotes shoving other things into the Controller and View. It is not a problem that PHP programmers usually have -- unless they use one of the Rails-Ripoff frameworks. It is a funny concept because it is a guideline to use in lieu of understanding int ins-and-outs of MVC, Model-Presentation, and Transaction Scripts.
I guess it is a catchy, shorthand way to imply "Put only Controller logic in the Controller; Put only Model logic in the Model -- And, there is usually much more Model logic."