Over the past 2 years I’ve used Zend Framework for several projects and played with the autoload functionality of Zend_Loader, as well as using standard include statements.
Ever since Zend released its first version of Zend Framework, discussions have been going on about what the best way to autoload files/classes is. Some people even claim autoloading is simply a bad idea and we should use include/include_once/require/require_once (another never ending debate there).
The discussion always seems to miss out on a very important point : Zend Framework is an MVC framework and, if used with the Conventional Modular Directory Structure with each module containing its own business logic and its own models, there’s no fixed models path to add to your include path. Autoloading can solve that issue, and many more…
Let’s compare the options we have at our disposal :
Option 1 : include all files in your bootstrap
Loading all your classes in the bootstrap will cause PHP to parse every single line of code you wrote, regardless of whether you’ll need it.
Benefits :
You have one place to go if some function can’t be found.
Drawbacks :
Many, but performance is certainly the biggest one. Why load thousands of lines of code if you’re only going to use a few ?
Verdict :
Please, please don’t do this !
Option 2 : use include_once at the top of each class
This is the old way of handling things and is still prefered by many. Basically, you include every file being used by the current file.
include_once "Wall.php";
include_once "Roof.php";
class House
{
private $_wallObj;
private $_roofObj;
public function __construct()
{
_wallObj = new Wall();
$this->_roofObj = new Roof();
}
}
Benefits :
- Works fine, old school, everyone will understand
- You can easily see which files/classes are being used in the current one
Drawbacks :
- It can be a hassle writing the include statements all the time (although you can ofcourse let Zend Studio handle this for you through the refactor functionality)
- More importantly, suppose you first want to build your walls and, if any money’s left, you’ll build a roof. Your code might look like this :
public function __construct()
{
$this->_wallObj = new Wall();
}
public function buildRoof()
{
$this->_roofObj = new Roof();
}
Now suppose Roof.php contains 5000 lines of code. If you include Roof.php regardless of whether the Roof class will actually be used, you will parse 5000 lines of code which will never be used, which is a serious waste of resources.
Verdict :
Don’t use it for object oriented development or anything else than the bootstrap
Option 3 : use include_once right before you use the class
class House
{
private $_wallObj;
private $_roofObj;
public function __construct()
{
include_once "Wall.php";
$this->_wallObj = new Wall();
}
public function buildRoof()
{
include_once "Roof.php";
$this->_roofObj = new Roof();
}
}
Benefits :
- It’s faster than putting all the includes at the top (if you’re not running buildRoof() ofcourse)
- It clearly marks which file contains which class (although your naving conventions should make that clear already !)
Drawbacks :
- If you create an instance of Roof in more than 1 method, you will need to include Roof.php each time to be sure the class has been loaded
- You can’t use any kind of automatic refactoring
Verdict :
Although similar to option 1, this option provides some performance gains, but it completely lacks code maintainability.
Option 4 : use Zend_Loader, standard implementation
class House
{
private $_wallObj;
private $_roofObj;
public function __construct()
{
$this->_wallObj = new Wall();
}
public function buildRoof()
{
$this->_roofObj = new Roof();
}
}
Benefits :
- No need to use any kind of includes
- No refactoring needed
Drawbacks :
- You need to add the models path for each module to your include_path
- If you have duplicate model names in different modules, you’re in trouble
Verdict :
Although it makes life easier, whenever you use the conventional modular directory structure with models in every module, autoloading can become a mess.
Option 5 : Zend_Loader with automatic include_path changes
I know there’s a proposal for a new action helper called ModelLoader, but I don’t like it, since you still have to list classes manually. I prefer to have all models for the current module available for autoloading in my controllers without having the bother updating listings every time a change is made.
This option is identical to the option 4, except that you don’t include any models and in the bootstrap you register a front controller plugin and define a constant which holds the base include path (or you could put it in Zend_Registry if you like, but that uses a lot more CPU cycles) :
$controller->registerPlugin(new Af_Controller_Plugin_LoadModels());
define('INCLUDE_PATH', get_include_path());
The Af_Controller_Plugin_LoadModels class looks like this :
class Af_Controller_Plugin_LoadModels extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
set_include_path(ROOT_DIR . '/application/modules/'.$request->getModuleName().'/models' . PATH_SEPARATOR . INCLUDE_PATH);
}
}
So what happens here ? Every time an a dispatch occurs, the include_path is prepended with the models path of module being accessed. As a result, the models can be autoloaded using Zend_Loader.
Why no postDispatch ? Don’t we need to reset the include_path ? We don’t, since any next dispatch will result in an override of the include_path anyway.
And in case you’re wondering why I’m using preDispatch() instead of dispatchLoopStartup() : if you use _forward inside any action, preDispatch() is run again, but dispatchLoopStartup() isn’t.
So if you forward from :
Module : firstModule
Controller : indexController
Action : indexAction
to :
Module : secondModule
Controller : indexController
Action: indexAction
all models for secondModule are available for autoloading when using preDispatch(), but they wouldn’t be when using dispatchLoopStartup().
On a side-note
There’s a lot of things you can do using front controller plugins : creating and (un)locking module-specific session namespaces, automatically handling ACLs, etc.
I’ll be getting back to some of those in future posts.
Conclusion
If you’re using the conventional modular directory structure in your Zend Framework projects, turn the autoloader on, so you can at least autoload your library classes automatically. If you want even more easy-of-use, use a front controller plugin to set the include_path for your models. Performance won’t be affected by it (if this is an issue, you have bigger problems with your code !) and you’ll be able to write code that’s quicker to write, more portable between projects (no need to refactor) and a joy to maintain !
Hi Wim,
I think you didn’t take into account the possibility to have a bytecode cache mechanism (which I strongly recommend in production) such as APC, eAccelerator, Zend accelerator,… Those bytecode cache mechanism change drastically the way (include|require)(_once) works!
Having such a configuration and not using the *_once() version to include files turns “Option 1” as the best one (performance and maintainability-wise).
Regards
I’m afraid I have to disagree there. Regardless of whether you use a bytecode caching system, option 1 is not recommended.
For starters, if you use option 1 with Zend Framework, you will have to include every single model in your bootstrap (and theoretically, every single Zend Framework class, but let’s skip that assumption). That means loading massive amounts of code (agreed, it’s bytecode, but nevertheless it’s still code) that will never get executed.
For example : I have a Zend Framework application with 11 modules, each holding several models, together holding over 6.000 lines of PHP code. Even when using eAccelerator that means a huge memory footprint, which is being loaded every single time someone accesses the site.
By using dynamic loading, you can still use bytecode caching and load only those models required for the specific task. This means performance gain and lower memory footprint, meaning your system can take a higher load.
You mention increased maintainability when using option 1. However, every single change within your project needs to be reflected into your bootstrap.
On the other hand, when using autoloading, you don’t have to worry about anything, meaning zero maintainability effort.
It gets even worse when you integrate things like Propel… if you need to start including all those classes as well, the memory footprint becomes massive.
Also, you might want to take a look at http://framework.zend.com/wiki/display/ZFDEV/Performance+-+Requiring+the+Autoloader
So…. to close. What is the best way to do this? I am new to zend framework and trying to understand the loader class.
I am using Zend_Loader_Autoloader::getInstance();
How could I assign my models folder so I can just call the classes?
If you don’t have any modules, but just 1 model directory, all you need to do is set the include_path to include that directory. You can do that in .htaccess or your virtualhost configuration.
The Zend_Loader will automatically load the model from that directory when you instantiate a new object of the class.
If you’re using multiple modules, use Option 5 of the article. It’s the most flexible and dynamic solution.
I have founded your article very intresting, i waswondering if you dont mind if i translate this one to spanish, and put it on my website with a link to yours.
thanks in advance whatever your answear is!
ps: sorry about my english!
Feel free to do so !
Zend auto loader is the biggest heap of crap I think that exists in the Zend Framework, possibly the PHP community at large.
It’s confusing, illogical, inconsistent and a bugger to setup. At least before with includes everywhere problems were easy to trace!
Why can I have a controller in a module load fine with Module_Controller and then when I try and extend another controller it can’t bloody find it?
Not being able to auto load models from modules easily is a joke.
This approach will ensure you are only installing what you actually need. As an example, if you are not using zend-barcode, or zend-permissions-acl, or zend-mail, there’s no reason to install them.