PHP, Zend Framework and Other Crazy Stuff
PHP General
Zend Framework Monthly Bug Hunt Starts Today – C’mon, Join In!
Sep 17th
As Matthew announced during the week on the mailing lists, Zend are sponsoring a two-day Bug Hunt every month starting today. And there will be prizes for those who solve lots of issues! Here’s Matthew’s email:
Greetings, one and all!
I’ve alluded several times in the past month to having a plan for
helping manage our ever-growing bug list in the issue tracker. We’re now
ready to roll out phase one of this plan, and we need *you*!Starting this month, we will be sponsoring two bug hunt days monthly, on
the third Thursday and Friday of the month. That’s this upcoming
Thursday and Friday, 17-18 September 2009.During those days, the Zend team — myself, Ralph, and Alex — will be
in #zftalk.dev on Freenode for our entire work day (Ralph and myself are
in the United States, Alex is based in Russia; figure out the timezones
yourself (-: ). We will be triaging bugs ourselves, but, more
importantly, we will be there to help facilitate *you*, our contributors
and users, in resolving issue reports.As an incentive, each month, we will ship a Zend Framework t-shirt to
the individual that assists in the most issue resolutions during the bug
hunt days, whether via patches or direct commits. Quarterly, we will
evaluate overall contributions, including documentation, bug fixes, and
newly contributed components, and award a developer with their choice of
a Zend Studio license or Zend Framework Certification voucher. (Caveat:
one t-shirt per person per year, and one license/voucher per person per
year, folks!)For those interested in participating in the bug hunt days, the rules
are simple: have a signed CLA on file, and resolve issues in the
tracker.If you have not yet signed a CLA and want to participate, you can get a
copy of the form here:http://framework.zend.com/cla
Sign it and return it (you can email it, fax it, or send it via post);
if you send it via post, you’ll need to wait for confirmation that
we’ve received it before we can accept code contributions from you.Now, when it comes to the issue tracker, you’ll need to determine if the
issue:* is simply the reporter misunderstanding or misusing code OR
* is a request for a new feature OR
* is a reproducible issueIn the first case, comment on it and indicate the correct usage, and ask
the component maintainer or somebody from Zend to review your response
and mark the issue as resolved. In the second case, please try and focus
on issue reports instead of feature requests during the bug hunt days.That brings us to the final case, reproducible issue reports. With
these, you’ll need to do the following:* Capture the reproduce case as a unit test
* Resolve the issue in such a way as to maintain backwards
compatibility with existing usage. (In other words, don’t change the
signature of a method unless the signature is what is actually
broken.)From there, you then have two options:
* If you already have commit access, commit the test and fix to the
repository, and either resolve the issue or ask somebody from Zend to
review and resolve. Don’t forget to merge your changes to the 1.9
release branch!* If you do not have commit rights, create a patch with the unit test
and fix, and attach the patch to the issue. Ask the maintainer or
somebody from Zend to review and apply the patch.If you need help creating the unit test or patch file, hop onto the
#zftalk.dev IRC channel and ask for help.How should you choose issues to work on? Answer the following questions,
and you should be able to hop right in:* What components do you have expertise in?
* What components are you interested in learning more about?
* What issues have a high number of voters or watchers?Bug hunting should be fun, so pick components and issues you’re
interested in. Ask questions on IRC if you don’t understand how
something works.So, spread the word, and come prepared this week to help make the
framework even better! I look forward to seeing you on IRC this week!
The Zend Framework has quite a large number of issue that need to be resolved/closed/marked invalid. People often have this fuzzy idea that somehow all three members of Zend’s framework team will miraculously resolve them for us in between developing components, fixing the documentation, handling releases, offering support, and…lots of stuff. This idea needs to die a quick death – the Zend Framework is an open source project so the people responsible for fixing its issues is the community.
So I truly encourage everyone to get involved. If you are not up to spending the next two days fixing issues, then strongly consider a more limited approach. Pick just one or two issues to fix. Surely that is a manageable and not overly time consuming commitment?
I’ll be around for most of today, and probably part of tomorrow so I look forward to seeing the resolution count reach staggering new heights
. Everyone involved may convene in the #zftalk.dev IRC channel on Freenode.net where Matthew and Ralph will be available to offer support to any Bug Hunter needing a query answered or advice on how to proceed with an issue.
Just a short note on using the Issue Tracker: It’s hosted on a server which for one reason or another performs quite poorly at times. If it seems to be taking ages to load, please be patient – it will…eventually.
Self-Contained Reusable Zend Framework Modules With Standardised Configurators
Sep 13th
It was during last week, while writing out a draft chapter for Zend Framework: Survive The Deep End, that I found myself hitting a conceptual wall. If you are familiar with Zend Framework, you likely understand the concept of a Module in some detail. A Module is, in theory, a reusable collection of controllers, views and other classes which is packaged in its own directory for simpler copying or seperate treatment in a version control system like git or subversion.
The problem I had lay in demonstrated this fabled reusability. The more I tried to, the more I found myself throwing out cautions, warnings and advice on what to avoid doing. When it came to using Zend_Application, the trend continued since Zend_Application (a great component otherwise!) is just badly documented and explained. So off went another section just to try and explain its often confusing terminology. If you read the source code it all makes sense but if you don’t the disconnect between the explanations and a user’s expectations is obvious.
Reusability Rules
Zend Framework developers have, for better or worse, been ignoring the potential of modules for an interminably long time. It’s not that big of a surprise given the focus of the framework has always been to present a use-at-will architecture which relies on loose coupling and independent components. Tight integration through overarching features (which don’t break the framework’s impressive orthogonality) like a command line tool or initialisation tools has long been neglected until very recently. Zend Framework 1.8 saw the long needed introduction of Zend_Application which offers standardised bootstrapping. Zend_Tool is another ongoing effort on the command line side.
The most typical example of a module in the literature is also the worst. An administration backend. It’s a logical module since it’s a completely separate system to the frontend, but it’s the worst example because it is so very rarely reusable. Not every logical separation is reusable – they are mutually exclusive concepts. You could equally have a logical module which itself is comprised of several reusable modules and one non-reusable module. By definition, an administration backend is closely tied to CRUD operations against the application’s domain model (at least to start with). Since each application will be different, the administration backend will also.
A far better example of a reusable module is something much narrower and focused. Consider a module dealing with User Management, or Paypal IPN integration, or implementing a blog aggregator. These are each common needs which, depending on the application, may require little change from implementation to implementation. Drop them in, configure them, integrate them, and you can have them working with few issues. Unfotunately, we keep focusing our module efforts on obviously non-reusable things like administration backends. Losing sight of the potential reuse of smaller subsystems will lead us to repeatedly developing them over and over again without even noticing this as a problem.
For the Zend Framework, this would be a big win. Rather than having developers re-implement commonly used web application systems it would encourage the distribution of third-party modules which would benefit from open source licensing and feedback. Imagine your next application requiring a minimal blog or integration with Paypal IPN and finding a third party module which does the trick so you can save some development time.
Achieving Reusability
When we discuss achieving reusability there are several factors and features covered when it comes to modules:
1. They are separated into their own parent directory.
2. They can apply specific configuration when accessed.
3. They require no special integration work.
4. Their classes are automatically available to the host application.
5. They are not required to contain controllers or views.
It’s not an exhaustive list. Items 1, 4 and 5 are already a reality. Zend Framework modules do live in a module directory, using Zend_Application and some conventions their classes are autoloaded on demand and they are not required to contain controllers and views. A module may exist which merely offers models, helpers and some default forms.
So our path to reusable modules is hampered by items 2 and 3. Modules currently don’t have on-access configuration unless we impose it through various means. This flows into integration work which is commonly needed to achieve this in the first place.
The Layout Example: Integration Through Front Controller Plugins
A simple example, taking our example administration backend (an “admin” module) is that of switching layouts. Suppose our main application uses a professional design but our administration backend uses a very simple minimal one. How do we switch layouts when the admin module is accessed so the correct layout template is applied?
An initial expectation might be to try this from our application.ini file (if using Zend_Application) using:
[geshi lang=css]; Default Module
resources.layout.layout = “default”
resources.layout.layoutPath = APPLICATION_PATH “/views/layouts”
; Admin Module
admin.resources.layout.layout = “default”
admin.resources.layout.layoutPath = APPLICATION_PATH “/modules/admin/views/layouts”[/geshi]
Ah, module configuration! This is a very common first attempt since the expectation is that a module framed configuration will kick in only for that module.
Alas, this will not work even if it looks blatantly obvious that it should (expectations again). Module configuration here is used during the bootstrapping process which occurs before a request is routed, i.e. we can’t know what module the request relates to yet because our routes are not yet applied. So any module configuration of this type is actually applied to the same resources as the previous set of settings, i.e. module configuration overwrites the main resource configuration. The example above, replaces the layout and layout path across the entire application with that of the admin module. Visiting any module, including the default module, will show the last configured layout being applied no matter what module prefixing you use.
What possible good is having this confusing module configuration then? Well, it’s useful to pass custom options to your module’s bootstrap class for something. Beyond that, I can’t think of many other use cases. You could, for example, use it to register module-hosted plugins, classes, etc but that’s just as easily done without the module name prefixed to the option. Note, this is my own ignorance speaking – I haven’t seen any detailed examples using this.
In the meantime, how do we ensure the layout is only switched if the module is accessed? The above configuration won’t work, obviously. Well, we first need to know what module is being dispatched to, so it must be done after routing has taken place. The most obvious location for our switching logic is therefore a front controller plugin which implements the preDispatch() method (i.e. it’s executed just before any controller is called, giving us an opportunity to re-configure some resources like Zend_Layout).
Here’s an example plugin for this. It’s the simplest possible version – I’ve seen some examples which forget that Zend_Layout already offers a plugin we can subclass to keep things simple.
[geshi lang=php]
class ZFExt_Controller_Plugin_LayoutSwitcher
extends Zend_Layout_Controller_Plugin_Layout
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->getLayout()->setLayoutPath(
Zend_Controller_Front::getInstance()->getModuleDirectory(
$request->getModuleName()
) . ‘/views/layouts’
);
$this->getLayout()->setLayout(‘default’);
}
}[/geshi]
It’s not a perfect class – every single module must follow the convention on using the same layout path and layout name. We could also add some logic to skip the default module since this would configure it twice for no reason. But it works! When we access the “admin” module, the layout path will be set to /application/modules/admin/views/layouts and the layout template used will be default.phtml. The default modules path will likewise reflect its original configuration.
To get this working, let’s add a new layout resource option so our custom plugin replaces the default one from Zend_Layout:
[geshi lang=css]; Default Module
resources.layout.layout = “default”
resources.layout.layoutPath = APPLICATION_PATH “/views/layouts”
resources.layout.pluginClass= “ZFExt_Controller_Plugin_LayoutSwitcher”[/geshi]
This does work by the way
. Add the following test alongside a directory _modules containing a readable subdirectory _modules/admin and it will pass.
[geshi lang=php]
class ZFExt_Controller_Plugin_LayoutSwitcherTest extends PHPUnit_Framework_TestCase
{
protected $plugin = null;
protected $request = null;
public function setup()
{
Zend_Controller_Front::getInstance()->addModuleDirectory(dirname(__FILE__) . ‘/_modules’);
$this->plugin = new ZFExt_Controller_Plugin_LayoutSwitcher(
new Zend_Layout
);
$this->request = new Zend_Controller_Request_Http;
}
public function teardown()
{
Zend_Controller_Front::getInstance()->resetInstance();
}
public function testSwitchesLayoutNameIfAdminModuleDispatched()
{
$this->request->setModuleName(‘admin’);
$this->plugin->preDispatch($request);
$this->assertEquals(‘default’, $this->plugin->getLayout()->getLayout());
}
public function testSwitchesLayoutPathIfAdminModuleDispatched()
{
$this->request->setModuleName(‘admin’);
$this->plugin->preDispatch($request);
$this->assertEquals(dirname(__FILE__) . ‘/_modules/admin/views/layouts’,
$this->plugin->getLayout()->getLayoutPath());
}
}[/geshi]
How about something different? What if our main application uses HTML 5 and our administration backend uses XHTML 1.0 Transitional. Damn, we need another plugin. Worse, this time we can’t reduce it to a convention since a doctype can be anything and we have no way of predicting it. We could set it on the module layout, but layouts are rendered last – it would still not be applied to page level templates or partials. Forms would be messed up, for example. Same goes for the character encoding of our views (messed up escaping).
So slap in another plugin to handle doctype switching, and another to handle encoding changes. Why not add another just for fun so we can handle connecting to a module’s shared database. Then there’s the case where… Alright…enough of that
. The point is a simple one. We are adding custom plugins all over the place to integrate modules into our application. These plugins will not be reusable, will require editing for different modules, and will need to be rewritten between applications. We need something more structured.
Integration: Modular Pre-Dispatch Configuration
As we can see, integration efforts are tricky. Relying on custom plugins and trying to wrestle the bootstrap system into submission are a lot of trouble to go through. Zend_Application and bootstrapping may not offer us a good solution for integration, but they do give us the roadmap.
Zend_Application defines bootstrap classes which are used to initialise resources before routing takes place. Keeping it simple, we need to reconfigure resources after routing but before dispatching occurs. We may also need to initialise different resources if they are used by a module, but not the main application. What we need is something like bootstrapping that occurs after routing. After a bit of thought, we might come to the conclusion that the current Resource classes of Zend_Application could live parallel to counterparts who exist not to initialise a Resource, but to modify a pre-initialised Resource by resetting its configuration. These are what I term Configurators, maybe not the best name, which mirror Resources.
Take a Configurator class for Layouts as an example:
[geshi lang=php]
class ZFExt_Application_Module_Configurator_Layout
extends Zend_Application_Resource_ResourceAbstract
{
public function init()
{
$layout = $this->getBootstrap()->getResource(‘Layout’);
$layout->setOptions($this->getOptions());
}
}[/geshi]
Our Configurator actually extends from Zend_Application_Resource_ResourceAbstract demonstrating its close relationship to a Resource. However, it does not create and initialise a Resource – it merely modifies the existing one by injecting a new configuration sourced from a module.
Where does this replacement configuration come from? I’ve decided to use a simple convention. If you want a module to impose its own configuration when, and only when, it is accessed then create that configuration in a file called module.ini located at /application/modules/admin/configs/module.ini. The configuration file could be any supported format, but I’ve used the INI format for simplicity. This would look like (using the typical environmental groups):
[geshi lang=css][production]
; Standard Resource Options
resources.layout.layout = “default”
resources.layout.layoutPath = APPLICATION_PATH “/modules/admin/views/layouts”
[staging : production]
[testing : production]
[development : production][/geshi]
So, we have the Configurator class, and the configuration file it will use. Let’s bind these together. We’ll start by putting in place a class whose role is to use a collection of options loaded from module.ini to instantiate and run a set of Configurator classes.
[geshi lang=php]
class ZFExt_Application_Module_Configurator
{
public function __construct(Zend_Application_Bootstrap_Bootstrapper $bootstrap,
Zend_Config $config)
{
$this->_bootstrap = $bootstrap;
$this->_config = $config;
}
public function run()
{
$resources = array_keys($this->_config->resources->toArray());
foreach ($resources as $resourceName) {
$options = $this->_config->resources->$resourceName;
$configuratorClass = ‘ZFExt_Application_Module_Configurator_’ . ucfirst($resourceName);
$configurator = new $configuratorClass($options);
$configurator->setBootstrap($this->_bootstrap);
$configurator->init();
}
}
}[/geshi]
As you can see, it is very simple. It takes a configuration, detects what Resources it applies to, instantiates relevant Configurators and executes them. It could be improved a lot by allowing for custom Resources and other such customisations but for now the basics will do nicely.
Earlier, we mentioned that the application only becomes aware of the current module when the request is routed. Therefore, to get this working we need to trigger the Configurators after routing (or prior to request dispatching). We also need to check if the current module has a module.ini file and also ensure we skip over the default module (our main application space might be reusable so this is an arguable point and probably should be allowed for).
We’ll accomplish this using a front controller plugin:
[geshi lang=php]
class ZFExt_Controller_Plugin_ModuleConfigurator
extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
$bootstrap = $front->getParam(‘bootstrap’);
$moduleName = $request->getModuleName();
if ($moduleName == $front->getDefaultModule()) {
return;
}
$moduleDirectory = Zend_Controller_Front::getInstance()
->getModuleDirectory($moduleName);
$configPath = $moduleDirectory . ‘/configs/module.ini’;
if (file_exists($configPath)) {
if (!is_readable($configPath)) {
throw Exception(‘modules.ini not readable for module “‘ . $module . ‘”‘);
}
$config = new Zend_Config_Ini($configPath, $bootstrap->getEnvironment());
$configurator = new ZFExt_Application_Module_Configurator(
$bootstrap, $config
);
$configurator->run();
}
}
}[/geshi]
If you’re still with me, and can piece this story together, you achieve a workflow as follows for the admin module when accessed from any URI like http://example.com/admin. I’ve skipped steps where not relevant.
1. Normal bootstrapping is completed with the layout being initially set using the application.ini options.
2. The request is routed. The module name “admin” is set internally.
4. ZFExt_Controller_Plugin_ModuleConfigurator::preDispatch() is called before dispatching commences (getting it done before other plugins can be addressed in the future).
5. The plugin detects /application/modules/admin/configs/module.ini and loads it as a Zend_Config instance.
6. The plugin instantiates ZFExt_Application_Module_Configurator, passes it the configuration and original bootstrap, and calls the new object’s run() method.
7. The Module Configurator assesses the configuration for resource names. For each resource detected, it instantiates a Resource Configurator like ZFExt_Application_Module_Configurator_Layout.
8. The Resource Configurator is executed and applies the new configuration to the existing Layout Resource thus overwriting the original configuration.
9. Dispatching occurs – the admin module’s requested action is rendered with the correct admin layout.
By itself, this seems like a lot of trouble to go through – except what if it becomes a Zend Framework feature? All of a sudden, countless custom plugins will meet their death and be replaced by a simple configuration file!
Conclusion
The goal of this article was to highlight the problems of achieving reusable modules and implement, as a proof of concept, at least part of the solution with an eye to encouraging greater discussion of where to go from here. I, for one, would love to see this included in the Zend Framework so we can get over the trend of relying on custom plugins and evolve towards a more standardised means of configuring modules.
If you’ve enjoyed the article please do add a comment and make suggestions on what could be improved or added as a feature. If I get enough positive feedback I’ll move this into a formal proposal (preferably with a partner or two
). If you’re interested in collaborating on a proposal addressing this let me know!
