In today’s post, we look at one of the simplest and most effective components of the Zend Framework that allows us to create extendable and extensible Zend Framework applications - Zend Application Resource Plugins (combined with the Strategy Pattern). If you want to ensure your apps can grow without heartache, read on.
Whether you have a big or small budget are time rich or poor, there’s always the pressure to build applications with the future in mind. This means that we, as developers, have to ensure that they are built around two key concepts:
- Extendability - capable of being lengthened
- Extensibility - capable of being protruded or stretched or opened out
No matter how much people tell us that “I only want a simple application”, after building more than a handful of applications and working with a number of clients, your experience will begin to tell you that they’ll often want more.
Now this can be a good thing. When you do a good job, and clients pay, you can be happily rewarded for your efforts. When they don’t is a matter for another time and post. What’s more, put your hand up if you know the valid truth of:
It’s cheaper to keep an existing client than to gain a new one
So amongst the multitude of other advice you’ll receive throughout your life, building applications that are readily extensible and extendable - in a simple, clean and cost-efficient manner - is essential to keeping input as low as possible, whilst maximising output, and accompanying client satisfaction (or boss satisfaction if you’re full-time employed).
How Do You Do It
Now, like all things, there are many ways to skin a cat. But as Malt Blue is all about learning the Zend Framework, I want to show you a way to build easily extendable and extensible applications with it.
To do this, I’m going to show you an approach based a component that’s already available within the context of the Framework, one of its most effective aspects in my, not so, humble opinion.
What is it? It’s none other than:
Let’s have a quick introduction then get started building a simple example. This will show why it’s so effective in building extensible applications.
Resource Plugins
Quoting the Zend_Application reference section from the Zend Framework Manual:
Zend_Application provides a bootstrapping facility for applications which provides reusable resources, common- and module-based bootstrap classes and dependency checking. It also takes care of setting up the PHP environment and introduces autoloading by default.
Whether it’s the managing translations that are rendered through the view templates; connecting to one or more databases so that we can manipulate data in an RDBMS; or accessing system-wide caches so that we can store application objects, results of database queries, view templates or something else entirely; resource plugins take care of common and reusable functionality that we should only need to configure once, not repeatedly in a multitude of different ways.
And implementing resource plugins as the cornerstone of how we work with the framework, allows us to have a clear and consistent interface to the various components that we use, yet at the same time, provide us with the ability to adjust how it works, under the hood, in so doing adjust to meet the changing needs of our users as the needs demand.
It’s all made possible because they’re based around …
The Strategy Pattern
Wikipedia defines the strategy pattern as follows:
the strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.1
In short, the strategy pattern provides resource plugins the capability to expose a resource to our controller actions, yet not be concerned with the underlying algorithm or implementation doing the work.
If you’ve used the Zend Framework for any length of time, you’ll be familiar with both the Db and Log resource plugins. Through the use of the strategy pattern in both of these resource plugins, we can use the same interface, yet:
- Change the database that we connect to - thanks to the underlying PDO adapter classes
- Use more than one logger - including file, memcache & mail
- Customise the connection for the particular adapters - such as the filter levels for the different log writers
Have a look at the following examples, taken with liberty, from the Zend Framework manual):
Configuring Database Access
resources.db.adapter = "pdo_mysql"
resources.db.params.host = "localhost"
resources.db.params.username = "webuser"
resources.db.params.password = "XXXXXXX"
resources.db.params.dbname = "test"
resources.db.isDefaultTableAdapter = true
|
We could instead export the schema and adjust it to work with PostgreSQL, SQLServer, Firebird, or SQLite, import the data and continue working. Our users wouldn’t know the difference - unless there was a performance drop.
Configuring Logging
resources.log.stream.writerName = "Stream"
resources.log.stream.writerParams.stream =
APPLICATION_PATH "/../data/logs/application.log"
resources.log.stream.writerParams.mode = "a"
resources.log.stream.filterName = "Priority"
resources.log.stream.filterParams.priority = 4
|
Say we migrated to a host that doesn’t have a persistent filesystem, such as cloudControl, we could change the configuration to use FirePHP, or we could write our own adapter to store the results in Memcache or MongoDB.
In both of these scenarios, we don’t have to change any of our controller code when we make changes in the underlying resource. New developers don’t need to be experts in the technology and we’re now bound to a specific technology or approach to solving a situation.
So that’s why I’m advocating that to build extensible and extendable Zend Framework-based applications, we need to use this approach as one of the key aspects in how we develop with it.
A Sample Scenario
Now it’s all well and good to talk about one, but what about when the rubber meets the road? As Danny Devito said to Arnold Schwarzenegger in Twins, “Money Talks and Bullshit Walks”.
So let’s consider a tangible scenario now. Let’s say that you’re building an application that needs to store files. Let’s say that people can register and upload their favourite recipes of what they cook to the interweb for all their friends and family to see - in the age of Facebook, Flickr etc, this is quite feasible.
Let’s say that it’s a proof of concept and you’re not sure if it’s going to take off or not, so you don’t want to bet the farm and over-invest at this early stage. But you want something that can at least be a reliable test bed. What do you do?
Well, you do like any sane-minded person would do - you keep it simple, using proven options and grow as your customers demand. So we’re going to write a resource plugin that allows us to store files.
Now no, it won’t be all singing and all dancing, but you’ll see just how easy it is to do for starters and secondly that you have a single, consistent programming interface to use in your controller actions - one that won’t require any changes later, but will be super flexible and capable.
Ready? Great. Let’s Go!
How Do We Build One
To do it is actually quite easy, but does involve a couple of steps. These are:
- Create an initial adapter that’s based around a basic interface, which all the other adapters will have to implement
- Create the resource plugin so that it can be made available to the framework
- Register the plugin so that Zend Framework is aware of it
- Configure it with the correct options
- Use it
So let’s get started and build our shiny new resource plugin:
1.Create the Core Files
Under the library folder create the following directory structure:
Sample/
File/
Storer/
Adapter/
|
Then, create a file called Interface.php in Sample/File/Storer that looks like the following:
interface Sample_File_Storer_Interface {
public function persist($userId, $filename);
public function update($userId, $oldFile, $newFile);
public function unlink($userId, $filename);
}
|
This defines three methods that each adapter must implement:
- persist (store)
- update and
- unlink (remove) files
Though we’re not implementing update and unlink in this example, they’re there for a sense of completeness. Next, create a file, Factory.php in Sample/File/Storer that looks like the following:
class Sample_File_Storer_Factory {
private function __construct() {;}
public static function factory($options = null)
{
$adapterClass = 'Sample_File_Storer_Adapter_' .
ucfirst(strtolower($options->adapter));
if (class_exists($adapterClass)) {
if (!empty($options)) {
$class = new $adapterClass($options);
} else {
$class = new $adapterClass();
}
return $class;
} else {
throw new Sample_File_Storer_Exception(sprintf(
'Specified adapter \'%s\' is not available', $adapterClass
));
}
return null;
}
}
|
This will return a Sample_File_File_Storer adapter class, based on the adapter type we set in the resource plugin configuration, which we’re setting up later. If an adapter class is not found, matching the one we specify, an exception will be thrown.
Next, create a file, Exception.php in Sample/File/Storer that looks like the following:
class Sample_File_Storer_Exception extends Exception {
}
|
This is a simple exception class, based off of the core PHP exception class. Then create a file, File.php in Sample/File/Storer/Adapter that looks like the following:
class Sample_File_Storer_Adapter_File
implements Sample_File_Storer_Interface
{
protected $_basepath;
public function __construct($options)
{
$this->_basepath = $options->basepath;
}
public function persist($userId, $filename)
{
if (!is_writable($this->_basepath)) {
return false;
}
if ($this->_prepareStorage($userId)) {
if (copy($filename, $this->_basepath . $userId . '/'
. basename($filename)) === FALSE) {
return false;
}
unlink($filename);
}
return true;
}
protected function _prepareStorage($userId)
{
if (!file_exists($this->_basepath . $userId)) {
if (mkdir($this->_basepath . $userId) === FALSE) {
return false;
}
}
}
}
|
This is the adapter, which is responsible for managing files on the filesystem. It utilises one configuration, basepath, which gives it a place in the filesystem where it will store files under.
2.Create a Plugin Resource
Now we need to create a file, Filemanager.php in Sample/Application/Resource that looks like the following:
class Sample_Application_Resource_Filemanager extends Zend_Application_Resource_ResourceAbstract
{
protected $_storageAdapter;
public function init()
{
return $this->initialiseResource();
}
public function setAdapter($adapter)
{
$this->_storageAdapter = $adapter;
return $this;
}
public function initialiseResource()
{
// get the resource options
$options = (object)$this->getOptions();
$this->setAdapter(
Sample_File_Storer_Factory::factory($options)
);
return $this->_storageAdapter;
}
}
|
This takes care of calling the factory method on Sample_File_Storer_Factory for the adapter that we’ve specified when we make use of the plugin. This affords us a simple interface for initialising the adapter class, irrespective of the one that is chosen.
3.Register the Plugin Resource
In application.ini, add the following configuration. This adds our resource plugin to the application.
; Sample resource plugins
pluginPaths.Sample_Application_Resource =
APPLICATION_PATH "/../library/Sample/Application/Resource"
|
Then add the following, which sets the adapter type, in this case ‘file’ and the basepath for storing files.
resources.filemanager.adapter = "file"
resources.filemanager.basepath = APPLICATION_PATH "/../public/videos/"
|
With all this in place, we’re ready to use it to store files throughout our application. So let’s use it.
4.Use It in Your Actions
In your controller action, add the following code:
$front = Zend_Controller_Front::getInstance();
$this->_fileManager = $front->getParam('bootstrap')->getResource('filestorer');
if ($this->_fileManager->persist($this->_auth->getIdentity()->id,
$form->videofile->getFileName())
) {
$this->_flashMessenger->addMessage('File post-processed');
} else {
$this->_flashMessenger->addMessage('File not able to be post-processed');
}
|
This will retrieve a copy of the resource plugin and attempt to use it to store a file. I’ve assumed here that you’re uploading a file using the Zend_Form file element and you’ve gone through the normal validation process.
Tech Note:
If you’re not familiar with the Zend_Form file element, have a look at the documentation in the manual. It’s not quite complete, but is worth reading - along with posts from Rob Allen.
But I Need Something More
So ok, the application’s up and running and going well, but you’re finding that the filesystem is just not keeping up with the demands of the application. No matter what kind of configuration you try, it’s just not up to the task.
So you want to test out mongoDB instead. It has built-in sharding, balancing and fail-over. You’ve done some research, you see that some pretty smart people are involved with it from the PHP community and believe that it will be a good candidate to allow you to grow in to the future. How do we use it instead of the filesystem adapter?
Well, without going in to the specifics of a mongoDB implementation, the steps involved would, roughly, be as follows:
- Create a new adapter class, called Sample_File_Storer_Adapter_Mongodb implementing Sample_File_Storer_Interface
- Change the resource configuration from file to mongodb
- Setup your local and production mongoDb servers, design your schema and migrate the existing files over
Like anything worthwhile, there’ll be more to it than this, but the fundamental aspect is that:
- We have a clear path and process to move from one backend adapter to another
- We have only a loose coupling to the backend adapter
- We have a clear and consistent interface in our code, which doesn’t have to change when the backend changes, nor in any way be aware of it
There You Have It
And there you have it. To be fair, it is a little bit of work, but you can see from this simple example, that we’re able to provide a reusable file persistence plugin that is:
- Available to all actions of all controllers in all the modules of our application.
- Has a consistent interface
- Accommodates our current needs
- Is able to grow in the future as our future needs demand
Now, ok I’m not aware of any silver bullet that will solve all the future demands and directions of your applications, but using the Zend Framework and resource plugins, you will go a long way towards future-proofing the ones that you create.
So, share your thoughts in the comments.
Image copyright © Atlantic
Join the discussion
comments powered by Disqus