Reinventing the MVC: Zend_Application
This is a help stack for the new Zend_Application bootstrap. It is not going to make a lot of sense for those who have not tried the Zend_Application/Bootstrap/Tool system or who do not intend to do so to use modules.
The bootstrap process of Zend Framework has always been a downside; while the overall MVC controller system produces very predictable, clear and mangeable code, configuring the core of the application produces a bugbear of gnarled code. "Bootstrap" refers to the bundle of code that occurs that enables the pathing of calls to controllers, views, etc., as well as enabling any system wide resources such as database connections, layout(s), etc. In the Zend Framework this involves a lot of defining of paths to enable properly located class files to load without the overhead of "include" statements.
The Zend_Application, in concert with the autoloader upgrade of recent versions (c. 1.9) of ZF gives a much more encapsulated treatment of resources and a configuration file driven system that is much more elegant. Note I did not say it was elegant -- just MORE elegant. (trying to stay positive.)
Its also an unsung win when using Zend_App patterend paths that your class loading will hit a lot less file paths and be a bit faster than it will using a custom pathing situation because every class has a single absolute route to load; you aren't stumbling through all possible locations given by your php include_path options.
Most of the fun happens when you employ modules. Modues are a useful clustering mechanism wherein all the resources that are interrelated are bundled in a "mini-MVC" cluster inside the modules folder.
However there are a few things to understand and reconcile; part of these are the new gifts (better auto-namespacing) and some of these are just "I spent a few hours on this -- here's some saved time at you."
The Keppens blog on Modular Zend Framework Applications is important to understand and read. He even provides source and a nice PDF of the system.
Here are a few "High level" concepts that are not completely spelled out or are important to emphasize.
The Bootstrap file in a module is a hook that adds all of the folders in the module to the autoload path. Module bootstrap files extends Zend_Application_Module_Bootstrap -- not the Zend_Application_Bootstrap_Bootstrap class. They don't necessarily have to have any custom methods. NO MODULE BOOTSTRAP == no autoloading of model/form/etc. files. However without a bootstrap, you CAN have functioning controllers, actions, and views. so if you get to a certain point in a modules development but can't load any model classes, the culprit is a missing bootstrap file. Here is an example of a basic bootstrap file in a module called "Ultimatum":
<?php
class Ultimatum_Bootstrap extends Zend_Application_Module_Bootstrap
{
}- Every model, form, etc. of every module is available to all modules provided you include this bootstrap.
- View helpers only autoload in the module in which they are defined. I would advise putting global view helpers in the default view/helper directory and manually adding this folder to the view helper namespace.*
- The default database connection can be easily bootstrapped. This configuration will allow you to use the Zend_Db system without having to manually intiailize individual tables' connections.
resources.db.adapter = "Php_mysql" resources.db.params.hostname = localhost resources.db.params.username = uuu resources.db.params.password = ppp resources.db.params.dbname = ddd
- As far as I can tell, the view object that the controllers manipulate is only instantiated at the controller level, which occurs after all the bootstrapping. At this point I do all the view preparation at the controller level still, not the bootstrap level.
- There is no such thing as a library at the modular level; if you want to add a library folder (or other shared resource folder) you have to add an autoloader for this folder in that module's bootstrap. ****
- You can add any functionality into a bootstrap file (base or modular) that you wish. Note that the return of the init method (see below) is saved in a registry by name. If you want to ensure a dependency chain of bootstrapped resources, you can call one bootstrap method from another:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap{
public function _initFoo()
{
// assuming new Foo_Class does some useful resource stuff
return new Foo_Class();
}
public function _initBar()
{
$foo = $this->bootstrap('foo');
return new Bar_Class($foo->timestamp());
}
}
Here are a few configuration settings for my root bootstrap file. Note that they use a constant APPLICATION PATH which is the full path to applications as a convenience.
resources.layout.layoutPath = APPLICATION_PATH "/layouts" resources.frontController.controllerDirectory = APPLICATION_PATH "/modules/default/controllers" resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" resources.frontController.throwExceptions = 1; resources.modules[] =
and yes, there is no value for resources.modules[]. This strange directive is important if you want to use modues; if its not there (or is set to something), your modules will not autoload.
Note that if you want to "switch on" or "switch off" modules, you can try setting modules to a set list of modules to focus on which modules get autoloaded.
Note that the stock (zend tool produced) arrangement of directories for ZF treats the default module as a "special case" putting its controllers, views and models in a global space. I prefer to keep it symmetric to the othe rmodules, so I use the controllerDirectory setting to put it there. This must be mirrored in the bootstrap where I define the default autoloader.
So wherever you want to put your default module (or call your default module) is okay as long as the default autoloader below and the controllerDirectory path above are kept up to date with your choice.
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initAppAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => '',
'basePath' => dirname(__FILE__) . '/modules/default',
));
return $autoloader;
}
protected function _initDefaultLoader()
{
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setFallbackAutoloader(true);
$loader->suppressNotFoundWarnings(false);
}
Note that while an a MODULE autoloader is created for each module automatically, the DEFAULT autolaoder must be created manually. Also the namespace of the default module is significant; if you want to give every resource in your default module a namespace (branded for your company for instance) you can change the namespace value to your company prefix. If you give 'Gamma_' as your default namespace, then your model classes must be named 'Gamma_Model_Carts', your forms must be named 'Gamma_Form_Cart', etc.
Autoloading -- beyond the controller
There are a lot of autoloading options made available by the module autoloading system. Note that these are enabled by the inclusion of a bootstrap file in the module's root. Without this file, the module will still work but the features below will not.
The default loader pulls in resources from the library. Note that default library loading must be enabled using the second controller above. This works no matter where in the application you are. (any module, any controller.)
The bootstrap will load a controller and its matching view as well, which is a second form of autoloading.
But the third and most interesting form of autoloading is the module autoloader, which gives the ability to use a wide variety of subfolders in a controller beyond the controller and view setup. If the mofulr is set up with a local bootstrap file you can now create files inside the default or modules' folders. For instance, if you set the default namespace as empty (as above), you can declare a file "application/modules/default/models/Users.php"** that contains a class Model_Users.*** You can also include forms in a forms folder,
If you have a module called "articles", and you create a file "application/modules/articles/model/Revisions.php", and it contains a class definition for Articles_Model_Revisions, it will also autoload. so, abstractly, the name covention is (Module_)?(Type_)(filename). You can subfolder your types as well using the zend namespace -- that is, you can create a folder path "applications/modules/default/models/purchases/receipts.php" for the autoloadable class Model_Purchases_Receipts.
Models even has a subfolder pre-definition; the file "application/modules/default/models/DbTable/Users.php" with a class definition for Model_DbTable_Users will also autoload.
The complete list of folderpaths and name conventions you can include in a bootstrapped module is determined by the module autoloader's resource type list:
public function initDefaultResourceTypes()
{
$basePath = $this->getBasePath();
$this->addResourceTypes(array(
'dbtable' => array(
'namespace' => 'Model_DbTable',
'path' => 'models/DbTable',
),
'form' => array(
'namespace' => 'Form',
'path' => 'forms',
),
'model' => array(
'namespace' => 'Model',
'path' => 'models',
),
'plugin' => array(
'namespace' => 'Plugin',
'path' => 'plugins',
),
'service' => array(
'namespace' => 'Service',
'path' => 'services',
),
'viewhelper' => array(
'namespace' => 'View_Helper',
'path' => 'views/helpers',
),
'viewfilter' => array(
'namespace' => 'View_Filter',
'path' => 'views/filters',
),
));
$this->setDefaultResourceType('model');
}
* I do this in controllers -- probably is some way to bootstrap this but I haven't figured out whether the view object is available during the bootstrap cycle.
** This will be true no matter wherever you have the default models folder, as long as your bootstrap is set up with a properly set-up default autoloader.
*** There is no assumption about what kind of class it is -- it can be a data row, a custom domain class or any other class -- it doesn't even technically have to be a model related class, if you don't mind having a slightly confusing naming system.
**** Off topic hint: if you put your layouts in the public directory you can bundle CSS/image resources with them as well.

Post new comment