Page 1 of 2

Zend Framework - modular layout questions

Posted: Mon Sep 10, 2007 5:50 pm
by Luke
I have set up my Zend Framework application in such a way that (according to the Zend Framework manual) allows for modularity. What I mean by this is that my application can be split up into directories containing modules that each do something related to my application. Supposedly if you have a blog, a sales management console, and an admin system, your directory structure would look something like:

Code: Select all

/myapp
    /default (module)
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts
    /blog (module)
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts
    /sales (module)
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts
    /admin (module)
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts
The problem is that generally my view helpers are going to be (for the most part) all in one central directory. Only when there is some specific functionality that could only possibly EVER be needed within a certain module would I put it in that specific module's /views/helpers/ directory. The same goes for a lot of the actual templates and filters, along with possibly models, just about everything. Is there some sort of convention on how to assemble a modular directory structure so that all of this stuff can be shared? The manual is pretty vague on this subject... at least what I've found on it.

Posted: Mon Sep 10, 2007 6:46 pm
by Begby
It is pretty flexible. You can play with the view object and the view renderer helper in your bootstrap to get it how you want it.

Key methods for the view are setScriptPath(), setFilterPath() and setHelperPath() (they all have an add equivelant as well).

For the ViewRenderer check out setViewScriptPathSpec().


My structure looks like this

Code: Select all

/controllers
 /clients
   ManageController.php
 /system
  AuthController.php
  someOtherSystemsController.php
 /default
  defaultController.php
  errorController.php

/views
 /_helpers
 /_filters
 /_layouts
 /clients
   /manage
    form.phtml
    index.phtml
 /default
  /error
    error.phtml
  /index
   index.html

etc. etc.

Posted: Mon Sep 10, 2007 6:48 pm
by Begby
Here are the relevant portions of my bootstrap. I am using an extended view class, but it uses Zend_View as the base so it should apply to yours except for the setLayout() method.

Code: Select all

/**
	 * Setup the view and view renderer
	 */
	$view = new zFCP_View_Layout() ;
	$view
		->setLayout('main.phtml')
		->strictVars((bool) $config->bootstrap->strictviewvars) 
		->setScriptPath(APPLICATION_ROOT.DIRECTORY_SEPARATOR.'app_admin/views/_layouts') 
		->addScriptPath(APPLICATION_ROOT.DIRECTORY_SEPARATOR.'app_admin/views')
		->setFilterPath(APPLICATION_ROOT.DIRECTORY_SEPARATOR.'app_admin/views/_filters') 
		->setHelperPath(APPLICATION_ROOT.DIRECTORY_SEPARATOR.'app_admin/views/_helpers', 'FCP_View_Helper_');
	
	$renderer = new Zend_Controller_Action_Helper_ViewRenderer( $view ) ;
	$renderer->setViewScriptPathSpec(':module/:controller/:action.:suffix') ;
	Zend_Controller_Action_HelperBroker::addHelper($renderer);
	
	/**
	 * Configure and run the front cotroller
	 */
	$front = Zend_Controller_Front::getInstance() ;
	
	$front
		->setParam							( 'registry', Zend_Registry::getInstance() )
		->throwExceptions					( (bool) $config->bootstrap->throwexceptions )
		->setBaseUrl						( FCP_BASE_URL )
		->setModuleControllerDirectoryName	( '' )
		// This plugin handles login authentication
		->registerPlugin					( new zFCP_Controller_Plugin_AdminAuth(zFCP_Auth::getInstance()) )
		->addModuleDirectory				( APPLICATION_ROOT.$config->bootstrap->modulepath ) ;
	
	$front->dispatch() ;

Posted: Tue Sep 11, 2007 12:56 pm
by Luke
I'm having some trouble here. I changed my directory to look like this:

Code: Select all

/app
    /models
    /modules
        /default
            IndexController.php
            ErrorController.php
        /vendors
            IndexController.php
    /views
        /_elements (basically the same as your layout dir)
        /_filters
        /_helpers
        /default
            /index
                index.phtml
            /error
                error.phtml
        /vendors
    config.php
Then the relevant bootstrap code:

Code: Select all

/**
         * Now we set up the front controller to accept multiple modules. This is 
         * a modular front controller set up.
         */
        $front->addModuleDirectory(MC2_Bag::getBaseDir() . DS . 'modules');
        $front->setModuleControllerDirectoryName('controllers');
        
        // @todo: find out what this actually does and possibly just enable it when debug is on
        $front->throwExceptions( (bool) $config->throwexceptions );

        $this->view->setScriptPath(MC2_Bag::getBaseDir() . DS . 'views/')
                   ->addScriptPath(MC2_Bag::getBaseDir() . DS . 'views/_elements')
                   ->setHelperPath(MC2_Bag::getBaseDir() . DS . 'views/_helpers')
                   ->setFilterPath(MC2_Bag::getBaseDir() . DS . 'views/_filters');
It keeps giving me errors like this:
Fatal error: Uncaught exception 'Zend_View_Exception' with message 'script 'index/index.phtml' not found in path (C:\htdocs\bagselect\po\app\views\_elements\;C:\htdocs\bagselect\po\app\views\)' in C:\htdocs\bagselect\po\library\Zend\View\Abstract.php:856 Stack trace: #0 C:\htdocs\bagselect\po\library\Zend\View\Abstract.php(764): Zend_View_Abstract->_script('index/index.pht...') #1 C:\htdocs\bagselect\po\library\Zend\Controller\Action\Helper\ViewRenderer.php(742): Zend_View_Abstract->render('index/index.pht...') #2 C:\htdocs\bagselect\po\library\Zend\Controller\Action\Helper\ViewRenderer.php(763): Zend_Controller_Action_Helper_ViewRenderer->renderScript('index/index.pht...', NULL) #3 C:\htdocs\bagselect\po\library\Zend\Controller\Action\Helper\ViewRenderer.php(810): Zend_Controller_Action_Helper_ViewRenderer->render() #4 C:\htdocs\bagselect\po\library\Zend\Controller\Action\HelperBroker.php(160): Zend_Controller_Action_Helper_ViewRenderer->postDispatch() #5 C:\htdocs\bagselect\po\library\Zend\Controller\Action.php(504): Ze in C:\htdocs\bagselect\po\library\Zend\View\Abstract.php on line 856
It seems as though I am unable to get it to recognize my module paths correctly. What am I doing wrong? :(

Posted: Tue Sep 11, 2007 1:12 pm
by Christopher
In your directory tree you don't have 'controllers' directories in each module.

ZF has grown one ugly, tacked on module system...

Posted: Tue Sep 11, 2007 1:28 pm
by Luke
I just added a controllers directory to each and dropped the controllers into them. It says the same thing. Nothing I'm doing seems to work. Are there bugs in the module system or something? This is lame. :(

Posted: Tue Sep 11, 2007 2:24 pm
by Begby
make the moduleControllersDirectoryName be '' like in my example

Posted: Tue Sep 11, 2007 2:33 pm
by Luke
OK, well it seems to be finding my controllers now, but it can't seem to find the views. (index/index.html doesn't exist)

If I add views/default to the view script path, it works fine, but only for the default module. Why can't it find the right module's files?

Posted: Wed Sep 12, 2007 3:41 am
by Maugrim_The_Reaper
Hi Ninja,

You need to do two things:

1. Configure a custom Zend_View instance for your common paths (only the common ones!)
2. Configure the ViewRenderer Action helper for the location of Module specific templates/helpers/filters

You almost have the first right - but you probably only need to "addScriptPath()" for _Elements. Adding a Module specific script path in the bootstrap won't help unless you add all Modules, and that of course is going to result in template name collisions down the line.

The trick is in 2. - configure the ViewRenderer to dynamically add a Module's view directory at runtime. Something like what Begby suggested is pretty standard:

Code: Select all

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"
$this->view->addScriptPath(MC2_Bag::getBaseDir() . DS . 'views/_elements')
           ->addHelperPath(MC2_Bag::getBaseDir() . DS . 'views/_helpers')
           ->addFilterPath(MC2_Bag::getBaseDir() . DS . 'views/_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->setViewBasePathSpec(MC2_Bag::getBaseDir() . DS . 'views/:moduleDir');

Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
I can't test this right now, but this should be workable. It's unavoidable that ViewRenderer will also add extra Module specific filter/helper locations based on the ViewBasePathSpec - I don't think this causes a problem really, and if it does you can subclass ViewRenderer's initView() method to fix fairly simply.

Posted: Wed Sep 12, 2007 9:45 am
by Begby
I think someone needs to rewrite the zend view/module stuff. It's total <span style='color:blue' title='I&#39;m naughty, are you naughty?'>smurf</span> that it is so hard to configure and setup custom modules and stuff. I spent days trying to get my bootstrap as above then several more days to extend the view to do layouts.

Posted: Wed Sep 12, 2007 9:59 am
by Christopher
Maugrim had a go recently at trying to get them to clean the View up. Ask him about his experiences ...

Posted: Wed Sep 12, 2007 11:45 am
by Maugrim_The_Reaper
You can disable ViewRenderer. A lot of folk are doing that and reverting back to controlling View with a more hands-on approach that makes no assumptions. The current system is here to stay though - I've managed to get most of my Zend_View Enhanced proposals accepted apart from layouts which will use Zend_Layout (and ViewRenderer).

The bootstrap is so much bigger than I remember from ZF 0.6 ;). Those were the simple days...

Posted: Wed Sep 12, 2007 6:14 pm
by wei
i think it may be a case of "http://en.wikipedia.org/wiki/Design_by_committee", frameworks should be designed under a dictatorship

Posted: Thu Sep 13, 2007 3:57 pm
by Luke
For some reason I'm having issues with the module layout again. The only way to get it to work was to define the default directory explicitly. I don't want to define all modules explicitly. Here is the relevant code. If I uncomment the commented lines, it doesn't work anymore.

Code: Select all

/**
         * 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->addModuleDirectory(self::getBaseDir() . DS . 'modules');
        //$front->setModuleControllerDirectoryName('');

        $front->dispatch();

Posted: Thu Sep 13, 2007 4:32 pm
by Luke
switching the order of the two method calls fixed the problem... :? :roll: