Wednesday, March 2, 2011

Zend Self Contained Modules

So I've been playing around with Zend lately; and since my last web project was back in college, I was surprised how much development has happened! Where was I when all of this happened? Back then there were no Joomla nor Drupal, only "hand-woven" CMS! Anyways, I'm here back to square one and trying to relearn everything; luckily/unluckily, I picked Zend as my framework of choice.

Compared to Yii, I was surprised that modules in Zend are very hard to implement. What I wanted was a way to initialize my resources using a module specific configuration file; and a way to run code specific for a module (i.e. add actions to an action stack). Thanks to guidelines from Matthew[1] and module configurators from Padraic[2]: I implemented my own module configurator.

Here is my directory structure:
./
application/
 configs/
  application.ini
 layouts/
modules/
 default/
  configs/
   module.ini
  controllers/
  forms/
  layouts/
  library/
 Configurator/
  Layout.php
  models/
  views/
  helpers/
  scripts/
  Bootstrap.php
 news/
  Bootstrap.php
  docs/
  library/
MyApp/
 Controller/
 Plugin/
  ModuleConfigurator.php
 public/
 tests/
Similar from [2], I registered a plugin at Bootstrap.php:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
 protected function _initControllerPlugins()
 {
  // Register module configurator so that it will run first and initialize the modules
  $frontController = Zend_Controller_Front::getInstance();
  $frontController->registerPlugin(new MyApp_Controller_Plugin_ModuleConfigurator(),1);
 }
}
The Module Configurator does the following:
class MyApp_Controller_Plugin_ModuleConfigurator extends Zend_Controller_Plugin_Abstract
{
  protected $_view;
  protected function moduleInit($_bootstrap, $_config)
  {
   // Run resource configurators

   $resources = array_keys($_config->resources->toArray());
   foreach ($resources as $resourceName)
   {
    $options = $_config->resources->$resourceName;
    $configuratorClass = $this->_view->originalModule . '_Library_Configurator_' . ucfirst($resourceName);
    $configurator = new $configuratorClass($options);
    $configurator->setBootstrap($_bootstrap);
    $configurator->init();
   }

  // Attach plugins
  $plugins = $_config->plugins->toArray();
  foreach ($plugins as $pluginName)
  {
   $frontController = Zend_Controller_Front::getInstance();
   $frontController->registerPlugin(new $pluginName());
  }
 }

 public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
 {
  // Get view
  $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
  $viewRenderer->init();
  $view = $viewRenderer->view;
  $this->_view = $view;
  // set up common variables for the view
  $view->originalModule = $request->getModuleName();
  $view->originalController = $request->getControllerName();
  $view->originalAction = $request->getActionName();
  $front = Zend_Controller_Front::getInstance();
  $bootstrap = $front->getParam('bootstrap');
  $moduleName = $request->getModuleName();
  $moduleDirectory = Zend_Controller_Front::getInstance()->getModuleDirectory($moduleName);
  $configPath = $moduleDirectory . DIRECTORY_SEPARATOR . 'configs' . DIRECTORY_SEPARATOR . 'module.ini';
  if (file_exists($configPath))
  {
   if (!is_readable($configPath))
   {
    throw new Exception('modules.ini is not readable for module "' . $moduleName . '"');
   }
   $config = new Zend_Config_Ini($configPath, $bootstrap->getEnvironment());
   $this->moduleInit($bootstrap, $config);
  }
 }
}
So basically, what it does is that it reads the module.ini of the module, then init() every resource. Also, attach plugins if given in the module.ini. So a resource initiliazor is just a realization of a Zend_Application_Resource_ResourceAbstract, like below:
class News_Library_Configurator_Layout extends Zend_Application_Resource_ResourceAbstract
{
 public function init()
 {
  $layout = $this->getBootstrap()->getResource('layout');
  $layout->setOptions($this->getOptions());
 } 
}
Here is an example of a module.ini:
[production]
resources.layout.layout = "layout"
resources.layout.layoutPath = APPLICATION_PATH "/modules/Core/layouts/scripts"
plugins[] = "MyApp_Controller_Plugin_ViewSetup"
Each module has a specific library by autoloading it from a module bootstrap, like below:
class News_Bootstrap extends Zend_Application_Module_Bootstrap
{
 protected function _initLibraryAutoloader()
 {
  return $this->getResourceLoader()->addResourceType('library','library','Library');
 }
}
If all of these seems advanced to you, check out the references below, these guys explained these stuffs in great detail and I don't want to steal their spotlight (actually I'm just too lazy!)

 If I miss something, just tell me! :)

 References:
 [1] http://www.weierophinney.net/matthew/archives/234-Module-Bootstraps-in-Zend-Framework-Dos-and-Donts.html
[2] http://blog.astrumfutura.com/2009/09/self-contained-reusable-zend-framework-modules-with-standardised-configurators/
[3] http://framework.zend.com/wiki/pages/viewpage.action?pageId=16023853
[4] http://blog.vandenbos.org/2009/07/07/zend-framework-module-config/
[5] http://binarykitten.me.uk/dev/zend-framework/177-active-module-based-config-with-zend-framework.html

No comments: