-
Modular Zend Framework Skeleton 2009
Posted on April 8th, 2009 9 commentsSo the application skeleton series proved to be more popular than I was expecting. Unfortunately after a couple of server migrations, the source code examples that were linked to those articles did not survive the trip. I’ve gotten a lot of comments to restore those examples so I decided to write a new article to illustrate the latest version of the application skeleton I’ve been using for my applications. Since I’ve gone over much of the design philosiphy in the previous articles I won’t go into too much detail about other than to demonstrate a solid way to design MVC applications using the Zend Framework.
For those who have no clue what I’m talking about, you can review the archives here:
http://blog.realmofzod.com/2008/03/19/modular-zend-framework-application-skeleton-part-2-crud/
http://blog.realmofzod.com/2008/06/06/modular-zend-framework-skeleton-part-3-modules/
The only major deviation I have made from the past versions of the skeleton is the migration from Zend_Db to Doctrine and the use of domain model programming which only affects the module model folders. For more on this, see my recent article about it.
First of all the folder structure hasn’t changed much from my previous incarnations:
- app/
- default/
- config/config.xml
- lib/
- Zend/
- FP/
- Doctrine/
- bootstrap.php
- cache.php
- config.php
- database.php
- Doctrine.php
- init.php
- locale.php
- logger.php
- mail.php
- modules.php
- view.php
- log/
- plugins/
- public/
- assets/
- .htaccess
- index.php
- tmp/
The public folder serves as the document root for the entire application and is the only folder that can be accessed from the web. It’s purpose should be to only house client-side assets, the .htaccess file and the front controller which is the only php file in that folder.
.htaccess
DirectoryIndex index.php RewriteEngine on RewriteBase / RewriteCond %{REQUEST_URI} !assets\/.* RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [QSA,NC,L]All we are doing here is redirecting any requests that do not directly access files in the assets folder to be processed by the front controller in index.php. This will pass along any query strings and paramters to the php.
index.php
-
-
-<?php
-
-
-
require_once(BASE_DIR . ‘lib/bootstrap.php’);
-
-
$main = Zend_Controller_Front::getInstance();
-
$main->throwExceptions(false);
-
-
//GO!
-
$main->dispatch();
-
-
?>
-
The front controller routes all requests to the appropriate controller. This file includes the bootstrap file which initializes the application platform and is then configured to handle all thrown exceptions by the error controller plugin simply by passing false to throwExceptions().
The core of the platform is still housed in the lib folder which breaks up the bootstrap process into individual files, each handling a particular subsystem (logging, database, locale, etc). Additionally, any third party libraries that are required by your application can be housed here (I usually have Zend, PHPTAL, and Doctrine living here). My general philosiphy regarding lib files is that they are 100% application agnostic and for no reason should they ever be edited to accomodate a particular application (Unless you are performing upgrades which can carry over to your next project). These files are all tied together and included in bootstrap.php. The guts of the platform is designed to be a black box that can be used in any situation.
bootstrap.php
-
-
<?php
-
-
set_include_path(BASE_DIR . ‘lib’ . PATH_SEPARATOR . BASE_DIR . ‘plugins’ . PATH_SEPARATOR . get_include_path());
-
-
require_once(‘Zend/Loader.php’);
-
-
Zend_Loader::registerAutoLoad();
-
-
require_once(‘cache.php’);
-
require_once(‘config.php’);
-
require_once(‘locale.php’);
-
require_once(‘logger.php’);
-
require_once(‘database.php’);
-
require_once(‘mail.php’);
-
require_once(‘view.php’);
-
require_once(‘modules.php’);
-
require_once(‘init.php’);
-
-
?>
-
As you can see this file serves only to configure the base include paths, initialize the autoloader and to require all the necessary subsystem files. This allows us to keep the front controller file simple by only having to require one file. I’ve seen alot of examples that extend the front controller in a custom bootstrap class but I find that this approach makes it difficult to break out different functionality into independent files.
Each file in the lib folder, as I’ve said, manages a particular piece of the platform. As each file is included, all the necessary Zend classes are created and configured, pulling their configurations from the platform configuration xml file located in config.
config.xml
-
-
<?xml version="1.0"?>
-
<configdata>
-
<global>
-
<mode>debug</mode>
-
</global>
-
<production>
-
<database>
-
<enabled>1</enabled>
-
<dsn>mysql://user:pass@localhost/zendapp</dsn>
-
</database>
-
<error>
-
<display>0</display>
-
</error>
-
<cache>
-
<enabled>1</enabled>
-
</cache>
-
<logger>
-
<enabled>1</enabled>
-
<file>log/production.log</file>
-
<priority>6</priority>
-
</logger>
-
<locale>
-
<default>en_US</default>
-
<timezone>America/Los_Angeles</timezone>
-
<date>MM.dd.YYYY</date>
-
</locale>
-
<mail>
-
<enabled>1</enabled>
-
<mode>sendmail</mode>
-
</mail>
-
<view>
-
<layout>1</layout>
-
<engine>FP_View_PhpTal</engine>
-
<ext>html</ext>
-
<config>
-
<compileDir>../tmp</compileDir>
-
</config>
-
</view>
-
<paths>
-
<upload>upload</upload>
-
<assets>assets</assets>
-
<images>assets/images</images>
-
<scripts>assets/scripts</scripts>
-
<stylesheets>assets/styles</stylesheets>
-
<fonts>assets/fonts</fonts>
-
<temp>../tmp</temp>
-
</paths>
-
</production>
-
<debug extends="production">
-
<cache>
-
<enabled>1</enabled>
-
</cache>
-
<error>
-
<display>1</display>
-
</error>
-
<logger>
-
<enabled>1</enabled>
-
<file>log/debug.log</file>
-
<priority>7</priority>
-
</logger>
-
<dojo>
-
<debug>1</debug>
-
</dojo>
-
</debug>
-
</configdata>
-
The xml is layed out in a way that implements staging, allowing the developer to switch between debug and production modes that allow for subsystems to behave differently in each state simply by changing the value in mode. Within each staging node, subsystem configs are broken out into individual nodes. Most subsystems can be toggled on and off with the ‘enabled’ value. For the sake of performance this file gets cached regardless of the staging mode so changes will only be reflected once the cached files have been cleared.
cache.php
-
-
<?php
-
-
-
Zend_Registry::set(‘cache’, $CACHE);
-
Zend_Registry::set(‘html-cache’, $HTMLCACHE);
-
-
function get_cache($pKey){
-
$config = Zend_Registry::get(‘config’);
-
if ($config->cache->enabled){
-
$cache = Zend_Registry::get(‘cache’);
-
return $val;
-
} else
-
return null;
-
}
-
-
function set_cache($pValue, $pKey){
-
if (Zend_Registry::get(‘config’)->cache->enabled){
-
$cache = Zend_Registry::get(‘cache’);
-
}
-
}
-
-
function start_html_cache($pId){
-
if (Zend_Registry::get(‘config’)->cache->enabled){
-
$cache = Zend_Registry::get(‘html-cache’);
-
} else
-
return null;
-
}
-
-
function end_html_cache(){
-
if (Zend_Registry::get(‘config’)->cache->enabled){
-
$cache = Zend_Registry::get(‘html-cache’);
-
$cache->end();
-
}
-
}
-
-
function remove_from_cache($pKey){
-
if (Zend_Registry::get(‘config’)->cache->enabled){
-
$cache = Zend_Registry::get(‘cache’);
-
}
-
return false;
-
}
-
?>
-
This file sets up everything we need to perform caching on objects and html. It defines a number of functions globally which can be used any in our application and will perform automatic serialization and key hashing. Cache files are stored centrally in the tmp folder and can be cleared simply by being deleted.
config.php
-
-
<?php
-
-
$cache = Zend_Registry::get(‘cache’);
-
-
/* Load from cache if possible */
-
/* Load config from global xml file */
-
$GLOBAL = new Zend_Config_Xml(BASE_DIR . ‘config/config.xml’,‘global’, true);
-
-
if ($mode != ‘testing’) $mode = $GLOBAL->mode;
-
-
$CONFIG = new Zend_Config_Xml(BASE_DIR . ‘config/config.xml’, $mode, true);
-
$CONFIG->merge($GLOBAL);
-
}
-
-
Zend_Registry::set(‘config’,$CONFIG);
-
?>
-
This file loads the platform configuration from the xml file and stores the config object in the Registry for other systems to access it. First the mode node is read to determine which staging configuration to load and then the appropriate configuration is loaded.
locale.php
-
-
<?php
-
-
$config = Zend_Registry::get(‘config’);
-
-
date_default_timezone_set($config->locale->timezone);
-
-
Zend_Locale_Format::setOptions(array(‘locale’ => Zend_Locale_Format::STANDARD, ‘date_format’ => $config->locale->date));
-
-
$locale_config = $config->locale;
-
-
$translater = new Zend_Translate(‘gettext’, BASE_DIR . ‘lang’);
-
-
foreach ($locale_config->languages as $locale => $language){
-
$translater->addTranslation(BASE_DIR . "lang/$locale.mo", $locale);
-
}
-
-
$translater->setLocale($_COOKIE[‘locale’]);
-
Zend_Registry::set(‘locale’, $_COOKIE[‘locale’]);
-
} else {
-
$translater->setLocale($locale_config->default);
-
Zend_Registry::set(‘locale’, $locale_config->default);
-
}
-
-
Zend_Registry::set(‘Zend_Translate’, $translater);
-
}
-
?>
-
Next the locale settings are applied. This probably deserves a topic all on its own but in a nutshell it sets the application locale default and if the locale config specifies the presence of translation files, a Zend_Translate object is initialized and loaded into the registry for the views to use.
logger.php
-
-
<?php
-
$config = Zend_Registry::get(‘config’);
-
-
if ($config->logger->enabled){
-
$LOGGER = new Zend_Log(new Zend_Log_Writer_Stream(BASE_DIR . $config->logger->file));
-
$LOGGER->addFilter($logfilter);
-
-
} else
-
$LOGGER = new Zend_Log(new Zend_Log_Writer_Null);
-
-
-
Zend_Registry::set(‘logger’, $LOGGER);
-
-
function logobject($pObj, $pEcho=false){
-
return htmlspecialchars_decode(Zend_Debug::dump($pObj, null, $pEcho));
-
}
-
-
function logerr($pSrc, $pMessage){
-
global $LOGGER;
-
$LOGGER->err("$pSrc: $pMessage");
-
}
-
-
function logwarn($pSrc, $pMessage){
-
global $LOGGER;
-
$LOGGER->warn("$pSrc: $pMessage");
-
}
-
-
function loginfo($pSrc, $pMessage){
-
global $LOGGER;
-
$LOGGER->info("$pSrc: $pMessage");
-
}
-
-
function logdebug($pSrc, $pMessage){
-
global $LOGGER;
-
$LOGGER->debug("$pSrc: $pMessage");
-
}
-
?>
-
If logging is enabled, a logger object is instantiated and placed in the registry. The default logger simply writes timestamped messages to a file in the log folder, otherwise a null log writer is created allowing logged messages to go into limbo if logging is not desired or increased performance is needed.
database.php
-
-
<?php
-
$config = Zend_Registry::get(‘config’);
-
-
if ($config->database->enabled){
-
require_once(‘Doctrine.php’);
-
-
$conn = Doctrine_Manager::connection($config->database->dsn);
-
-
Zend_Registry::set(‘database’, $CONN);
-
}
-
-
?>
-
This file creates and tests the database connection manager.
mail.php
-
-
<?php
-
$config = Zend_Registry::get(‘config’);
-
-
if ($config->mail->enabled){
-
-
/* If mail setting set to smtp initialize smtp transport */
-
if ($config->mail->mode == ’smtp’){
-
Zend_Loader::loadClass(‘Zend_Mail_Transport_Smtp’);
-
$transport = new Zend_Mail_Transport_Smtp($config->mail->smtp->server, $config->mail->smtp->auth);
-
Zend_Mail::setDefaultTransport($transport);
-
}
-
}
-
?>
-
This file configures the default mail transport adapter. By default, it relies on the built in sendmail functionality that Zend_Mail uses to defer to the server’s mail transport. Alternatively, it can be configured to use a configured smtp transport.
view.php
-
-
<?php
-
-
function getViewEngine($pViewConfig){
-
-
$ViewPluginClass = $pViewConfig->engine;
-
$ViewPluginConfig = $pViewConfig->config->toArray();
-
-
if ($pViewConfig->layout){
-
Zend_Layout::startMvc();
-
Zend_Layout::getMvcInstance()->setViewSuffix($pViewConfig->ext);
-
}
-
-
//Initialize View Template Engine
-
-
$view_engine = new $ViewPluginClass($ViewPluginConfig);
-
-
return $view_engine;
-
}
-
-
$CONFIG = Zend_Registry::get(‘config’);
-
-
$view_engine = getViewEngine($CONFIG->view);
-
-
$vr = new Zend_Controller_Action_Helper_ViewRenderer();
-
$vr->setView($view_engine);
-
$vr->setViewSuffix($CONFIG->view->ext);
-
Zend_Controller_Action_HelperBroker::addHelper($vr);
-
-
$DocTypeHelper = new Zend_View_Helper_Doctype();
-
$DocTypeHelper->doctype(‘XHTML1_STRICT’);
-
-
$view_engine->addHelperPath(BASE_DIR . ‘lib/FP/View/Helpers/’, ‘FP_View_Helper’);
-
?>
-
The view subsystem configures Zend’s view module with the plugin of our choice. Our configuration implements a PHPTAL view plugin but it can easily be swapped out with something like Smarty or even left to the default phtml implemenation. View plugin specific configuration options are passed into the getViewEngine function which is globally accessible, allowing different application modules within our application to utilize different view engines. An example of why this is useful is having our main web application use PHPTAL for displaying templates, however, perhaps we have an enhanced mail module which utilizes Smarty to render email templates. In addition to configuring the view engine, this file also sets various defaults for some of the view helpers such as the default doctype, and additional view helper paths. I store highly reusable view helpers in the lib folder also, so we include that folder in our view helpers path as well to make them available globally.
modules.php
-
-
<?php
-
interface FP_Application_Module
-
{
-
public function getTitle();
-
public function getName();
-
public function getVersion();
-
public function getAuthor();
-
public function getEmail();
-
-
public function init();
-
-
public function getRoutes();
-
}
-
-
abstract class ApplicationModuleBroker
-
{
-
-
-
//Merge module config
-
if (!$module_config = get_cache("config_$pModule")){
-
$modxml = BASE_DIR . "app/$pModule/config/config.xml";
-
$module_config = new Zend_Config_Xml($modxml, $pConfig->mode, true);
-
set_cache($module_config, "config_$pModule");
-
}
-
}
-
$pConfig->merge($module_config);
-
self::$_configs_loaded[] = $pModule;
-
$mconfig = $module_config->toArray();
-
$defer_load = false;
-
foreach ($mconfig[$pModule][‘deps’] as $depname => $depversion){
-
$defer_load = true;
-
}
-
}
-
if ($defer_load)
-
return;
-
}
-
}
-
}
-
-
//Add models to include path
-
}
-
-
//Add lib to include path and require all php files inside
-
-
Zend_Loader::loadFile(BASE_DIR . "app/$pModule/lib/module.php");
-
$ModuleTitle = implodeCase(explode(‘_’, $pModule)); //Takes multi_word_module and converts to MultiWordModule
-
$ModuleClass = "{$ModuleTitle}Module";
-
-
if (!$module = get_cache("fpmodule_$pModule")){
-
-
$module = new $ModuleClass();
-
throw new Exception("Unable to instantiate module $pModule");
-
set_cache($module, "fpmodule_$pModule");
-
} else
-
throw new Exception("$pModule is not a module");
-
}
-
self::$_modules[$module->getTitle()] = $module;
-
self::$_modules2[$pModule] =& self::$_modules[$module->getTitle()];
-
$fc = Zend_Controller_Front::getInstance();
-
$router = $fc->getRouter();
-
$router->addRoutes($module->getRoutes());
-
$module->init();
-
-
} else
-
throw new Exception("$pModule is not a module");
-
-
//Add plugins to include path and require all contents
-
require_once(BASE_DIR . "app/$pModule/plugins/$plugin");
-
}
-
}
-
-
foreach (self::$_dependencies as $i => $dep){
-
if ($dep[‘dependency’] == $pModule){
-
$depmod = self::$_modules2[$pModule];
-
if ($depmod->getVersion() >= $dep[$pModule][‘version’]){
-
self::registerModule($dep[‘dependent’], $pConfig);
-
}
-
}
-
}
-
}
-
-
return self::$_modules;
-
}
-
-
return self::$_modules[$pName];
-
}
-
-
}
-
-
$modules = self::getModules();
-
foreach ($modules as $module){
-
$module->postInit();
-
}
-
}
-
}
-
-
$CONFIG = Zend_Registry::get(‘config’);
-
$CACHE = Zend_Registry::get(‘cache’);
-
/* Scan app modules folder and add model folders to php include path */
-
-
-
if ($entry != ‘default’)
-
ApplicationModuleBroker::registerModule($entry, $CONFIG);
-
}
-
}
-
-
ApplicationModuleBroker::registerModule(‘default’, $CONFIG);
-
ApplicationModuleBroker::postInit();
-
-
Zend_Registry::set(‘config’, $CONFIG);
-
-
$main = Zend_Controller_Front::getInstance();
-
$main->addModuleDirectory(BASE_DIR . ‘app’);
-
$main->setModuleControllerDirectoryName(‘controllers’);
-
?>
-
This subsystem is the heart of the platform. In a nutshell the purpose of this file is to scan the app folder for application modules and load them. Loading them implies extending our include path to make module lib files available as well as module specific models. The easiest way for me to explain what the module loader is doing is to break it down into steps.
1. Scan app folder
2. For each subfolder in the app folder, call registerModule on the module broker, passing in the name of the module folder and pass by reference the global config object.
3. If the module has not already been loaded, look for the module’s config xml file located in ‘app/$modulefolder/config/config.xml’. Load the appropriate staging config for that module and merge it into the global config object. This allows us to access the configuration for any application module using the same config object.
4. Determine if this module has dependencies it requires before it can load. This means some app modules might require classes defined by other app modules. If the dependency module has not been loaded yet, the current module load can be deferred until the necessary dependencies are in place. If the load is deferred, registerModule() simply returns. Otherwise, we continue.
5. Include the module’s models folder in the include path.
6. Add the module’s lib folder in the include path. Inside that folder, we expect to find a file called module.php which contains the application module class we need to load. Every valid module should have this file and the appropriate class defined inside. The module class name is constructed using the module folder name. If the module folder is ‘blog’, then the module class name is ‘BlogModule’. Alternately, if the module folder is ‘news_feed’, then the module class name is ‘NewsFeedModule’. It will always follow this convention.
7. Once we have the module class name we then instantiate the class. If all goes well, we should have a module object now that we can query for information we need about the module. Specifically we want to know what routes the module provides. All modules must implement the interface defined in our modules.php file which enforces the implementation of two critical module methods: getRoutes() and init(). The getRoutes() method returns an array of Zend_Controller_Router_Route objects which are registered with the front controller.
8. Call init() on the module. Since application modules get cached to boost performance, init gets called to initialize transient variables that cannot carry over between requests.
9. Scan list of deferred modules that are still waiting for dependencies to load. If all dependencies for a deferred moule are in place, call registerModule() again for that module.
This happens for each folder in the app folder, the only special case being the ‘default’ folder which always gets called last. the default module is our custom application and it gets loaded last because it will always depend on the other modules to be loaded first. After all the modules have been loaded, we do one more sweep and call postInit() on any modules that offer the method. This is a great way to do processing on all of the modules once they are all loaded. It might help to understand this better once I demonstrate an example of an application module. See below for the example.
The last file to get parsed in the bootstrap is init.php
init.php
-
-
<?php
-
-
-
$config = Zend_Registry::get(‘config’);
-
$front = Zend_Controller_Front::getInstance();
-
-
$baseUrl = $front->getBaseUrl();
-
-
Zend_Registry::set(‘docroot’, $_SERVER[‘DOCUMENT_ROOT’]);
-
Zend_Registry::set(’site’, $config->default->site);
-
Zend_Registry::set(’server’, ((array_key_exists(‘HTTPS’, $_SERVER) && $_SERVER[‘HTTPS’]) ? ‘https://’ : ‘http://’) . $_SERVER[‘SERVER_NAME’]);
-
Zend_Registry::set(‘paths’, $config->paths);
-
Zend_Registry::set(‘assets’, "$baseUrl/{$config->paths->assets}");
-
Zend_Registry::set(’scripts’, "$baseUrl/{$config->paths->scripts}");
-
Zend_Registry::set(’stylesheets’, "$baseUrl/{$config->paths->stylesheets}");
-
Zend_Registry::set(‘images’, "$baseUrl/{$config->paths->images}");
-
Zend_Registry::set(‘fonts’, "$baseUrl/{$config->paths->fonts}");
-
Zend_Registry::set(‘upload’, $config->paths->upload);
-
?>
-
This file is more or less a spill over for bootstrap operations that either don’t go any where else or rely on all the subsystems being loaded already. As you can see I use it for assigning a boat load of convenience varialbe to the registry for use throughout the application.
So now I want to demonstrate the structure of a real world application module. I’ll demonstrate my ACL module I recently wrote to handle security for most of my applications. It’s purpose is to generate Access Control Lists to manage access to secure resources, roles, users, and permissions. Here is it’s folder structure:
- app/
- acl/
- config/config.xml
- controllers/
- lib/
- module.php
- models/
- views/
- helpers/
- layouts/
- templates/
- default/
- …
- acl/
config and lib are technically the only folders required for a functional module but usually you will need controllers and views as well. Each module is comprised of it’s module class, a config.xml file, some controllers, some models, and view components which are usually templates, helpers, and layouts. They are arranged heirarchichly like those so they can be simply dumped into the app folder drag’n'drop like. Application modules act very much like plugins for your main web application. They can provide a variety of functionality such as providing forum features, blog features, content management, etc, whatever you can imagine.
The heart of a module is the module class defined in ‘lib/module.php’
acl/lib/module.php
-
-
<?php
-
class AclModule implements FP_Application_Module, Acl_Client_Interface
-
{
-
const VERSION = 2.0;
-
const DATE = ‘03/31/2009′;
-
const AUTHOR = ‘Brandon Clark’;
-
const EMAIL = ‘brandon.clark@eroi.com’;
-
const TITLE = ‘Access Control’;
-
const NAME = ‘acl’;
-
-
-
public function __construct(){
-
‘acl_admin’ => new Zend_Controller_Router_Route(‘acl/:tab’, array(‘controller’ => ‘index’, ‘action’ => ‘admin’, ‘module’ => ‘acl’, ‘tab’ => null)),
-
‘acl_synchronize’ => new Zend_Controller_Router_Route(‘acl/synchronize’, array(‘controller’ => ‘index’, ‘action’ => ’synchronize’, ‘module’ => ‘acl’)),
-
‘acl_acl_admin’ => new Zend_Controller_Router_Route(‘acl/acl/admin/:page’, array(‘controller’ => ‘acl’, ‘action’ => ‘admin’, ‘module’ => ‘acl’, ‘page’ => 1)),
-
‘acl_acl_permissions’ => new Zend_Controller_Router_Route(‘acl/acl/permissions/:acl_id/:page’, array(‘controller’ => ‘acl’, ‘action’ => ‘aclpermissions’, ‘module’ => ‘acl’, ‘page’ => 1)),
-
‘acl_acl_create_permission’ => new Zend_Controller_Router_Route(‘acl/acl/create-permission/:acl_id’, array(‘controller’ => ‘acl’, ‘action’ => ‘createpermission’, ‘module’ => ‘acl’)),
-
‘acl_acl_create’ => new Zend_Controller_Router_Route_Static(‘acl/acl/create’, array(‘controller’ => ‘acl’, ‘action’ => ‘create’, ‘module’ => ‘acl’)),
-
‘acl_acl_edit’ => new Zend_Controller_Router_Route(‘acl/acl/edit/:id’, array(‘controller’ => ‘acl’, ‘action’ => ‘edit’, ‘module’ => ‘acl’)),
-
‘acl_acl_delete’ => new Zend_Controller_Router_Route(‘acl/acl/delete/:id’, array(‘controller’ => ‘acl’, ‘action’ => ‘delete’, ‘module’ => ‘acl’)),
-
);
-
-
array(‘acl’ => ‘Controller Access Control’, ‘name’ => ‘Synchronize Acl Modules’, ‘resource’ => ‘acl_index’, ‘privilege’ => ’synchronize’),
-
);
-
-
}
-
-
//FP_Application_Module Interface=================================================
-
-
public function init(){
-
//Initialize ACL Plugins
-
$plugins = Zend_Registry::get(‘config’)->acl->plugins->toArray();
-
foreach ($plugins as $name => $plugin_config){
-
if ($plugin_config[‘enabled’]){
-
$plugin_class = "Acl_Plugin_{$name}";
-
}
-
}
-
}
-
-
public function getTitle(){
-
return self::TITLE;
-
}
-
-
public function getName(){
-
return self::NAME;
-
}
-
-
public function getVersion(){
-
return self::VERSION;
-
}
-
-
public function getAuthor(){
-
return self::AUTHOR;
-
}
-
-
public function getEmail(){
-
return self::EMAIL;
-
}
-
-
public function getRoutes(){
-
return $this->_routes;
-
}
-
-
//END FP_Application_Module Interface==============================================
-
-
//IACLClient Interface=============================================================
-
-
public function getPermissions(){
-
return $this->_permissions;
-
}
-
-
//END IACLClient Interface=========================================================
-
-
}
-
?>
-
I have snipped out much of the implementation to remain in scope of the article so you can see the methods that comply with the FP_Application_Module interface. These are the minimum methods needed to be loaded by the module loader.
app/acl/config/config.xml
-
-
<?xml version="1.0"?>
-
<configdata>
-
<production>
-
<acl>
-
<enabled>1</enabled>
-
<mode>allow</mode>
-
<backend>Db</backend>
-
<guest>Guest</guest>
-
<list>
-
<limits>10,25,50,100</limits>
-
<default_limit>50</default_limit>
-
</list>
-
<plugins>
-
<Controller>
-
<enabled>1</enabled>
-
<exempt>
-
<error>default_error</error>
-
</exempt>
-
</Controller>
-
</plugins>
-
</acl>
-
</production>
-
<debug extends="production">
-
</debug>
-
</configdata>
-
The specifics of the configuration info specified in this file isn’t so important as it is completely arbitrary and tailored to what the individual module expects to find there. The key is to make sure that the configuration is contained within a node that shares the name of the module folder, ‘acl’ in this case so it can be properly merged into the global config object and be readily accessed by name.
-
-
<?php
-
-
$enabled = Zend_Registry::get(‘config’)->acl->enabled;
-
-
?>
-
So there’s a number of classes I covered in the archived post but not in this one and that’s only because of the sheer volume of code and I didn’t want this post to become too overwhelming. The older posts were intended to be an introduction to Zend Framework MVC methodologies whereas this post is mainly a refresher on the latest methods I use and should build on those topics. To fill in the gaps, i’ve made the code available for a skeleton app available here so everyone can review the code and re-use it for their own projects.
Development, PHP, Tech, Zend Framework application, examples, framework, modules, MVC, php, skeleton, zendRelated Topics
9 responses to “Modular Zend Framework Skeleton 2009”
-
Hi brandon,
i find your article very useful. I read the three archives article too. But for me it would be useful to see the code in these articles. Maybe u can put the code from the archives articles back on.
mfg Alex
-
I can try but the truth is a lot of that code isn’t really valid any more and the idea was for this article to replace those. If your looking for how to do something specific I would be happy to go into depth about it using the updated skeleton.
-
Lots of good information here, thanks for sharing. I’m curious, what does the FP at the beginning of most of your classes stand for?
-
Haha… it’s a mystery isn’t it
-
Very nice.
Thanx.
-
Brandon,
Thank you for these informations, i`m looking for it for a long time, i will try use it in a test project, but i will try with Zend_Db.
Man, thaks again and congratulations you write very well, i`m brazilian and my english is not very good, but i coud understand everything.
Chees[].
-
A small improvement ;-)
On line 132 from modules.php i get the warning:
“bool assign : assignment in condition:
The line :
while ($entry = readdir($moddir)){
……..}
should be:
while (false !== ($entry = readdir($moddir))) {
……..
}Thanks, very nice article!
-
Hi Brandon,
GREAT work!
I love the structure of you skelleton and would like to use it as a base to start learning Z F.
Its weired…
For some reason i cant figure out how to create
a new modules and register it.I always get redirected to the error page.
Could you please show a demo with modules setup ?
Help is MUCH appreciated.
Kind Regards,
Marcel
-
Adam Stotes January 14th, 2010 at 22:28
Hey Brandon,
I’ve recently attempted to implement your 2009 Zend Framework Skeleton, with Zend Server CE.
According to the Debug, the modules have been loaded in.
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading blog
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading config for blog
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering models for blog
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering lib files for blog
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loaded module blog (blog)
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading reporting
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading config for reporting
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering models for reporting
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering lib files for reporting
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loaded module reporting (reporting)
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading default
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loading config for default
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering models for default
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Registering lib files for default
2010-01-15T15:12:42+11:00 DEBUG (7): ApplicationModuleBroker.registerModule: Loaded module default (default)However when attempting to access any modules other than the default module, I am getting a “no routes” error. From my understanding of your post and from reading through the modules.php in the bootstrap the module should be loaded into the routes already?
I’ve tried a few different attempts to figure out what is wrong with the routing but to no avail.
If you could provide any help or guidance, it would be very much appreciated.
Thanks,
Leave a reply
- app/


