application controller and application initializer

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

application controller and application initializer

Post 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()?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post 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 :)
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post 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.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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. :)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post 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. :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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)
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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?? :)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post 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?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Havn't looked into the links you've provided yet, but I will do so tommorow.

Thanks, as usual, Maugrim.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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."
(#10850)
Post Reply