Archive for the ‘PHP’ Category
Load custom elements with Zend_Form
My previous post talked about Zend_Plugin_Loader and how to use it in your code. In that post, I also mentioned that Zend_Plugin_Loader is used quite a bit throughout the Zend Framework code. In this post, I’m going to show you how to make use of the plugin loader to load your own custom elements into Zend_Form.
Zend_Form uses factory methods like createElement and addElement in order to simplify adding elements to your form. Using these methods, you can refer to default Zend Form elements by name and they will be instantiated for you and added to your form. For instance, to create a form with text field, your code might look something like this:
1 2 | $form = new Zend_Form; $form->addElement('text', 'user_name'); |
Behind the scenes, Zend_Form is using a plugin loader to look for the “text” element, which will then be instantiated and added to the form. In this case, you would get an instance of Zend_Form_Element_Text. What if you wanted to use your own custom text element instead? Your first instinct might be to do something like this:
1 2 3 | $form = new Zend_Form; $user_name = new My_Form_Element_Text('user_name'); $form->addElement($user_name); |
This works fine, and for small forms is probably the way to go. However, making use of the built in plugin loaders in Zend_Form (I think) leads to a little cleaner and more flexible solution. Here’s what the same example looks like using Zend_Form’s plugin loader:
1 2 3 | $form = new Zend_Form; $form->addPrefixPath("My_Form", "My/Form/"); $form->addElement('text', 'user_name'); |
Your form instance will now look for My_Form_Element_Text first before loading the default Zend_Form elements. This also takes care of setting up the plugin loader for decorators as well. This is nice because you don’t have to change your actual form code beyond adding the new prefix paths. This allows your forms to be much more flexible with respect to what elements and decorators are loaded.
Plugin loaders for element validators, filters, and decorators
One last bit, similar to the form instance itself, each element also has plugin loaders for decorators, filters and validators. Conveniently, you can set the prefix path for all elements added to a form using the Zend_Form::addElementPrefixPath method. This is where you’ll begin to see a lot of the power of Zend_Form, loading custom validators for your elements just as you would any other validator. It’s fun stuff!
Conclusion
You now have a way to load your own custom elements and decorators for forms, and a way to load decorators, filters and validators for your custom (or standard) elements. Enjoy!
Lazy class loading with Zend_Loader_PluginLoader
In applications built on top of a library of reusable code, you may want to load your custom class components before those of the library. For example, your library has a user model of Lib_Model_User. In general, this class is enough for your needs, but for a particular case, you’d like to use a custom implementation specific to your application called Custom_Model_User. Furthermore, say you have a controller that needs the user model to perform some action, say Lib_Controller_User. Typically, your code for that controller might look something like this:
1 2 3 4 5 6 7 8 9 10 | class Lib_Controller_User extends Lib_Controller { public function registerAction() { if (!empty($_POST)) { $model = new Lib_Model_User; $model->save($_POST); } } } |
For your particular app, you want to use Custom_Model_User instead of Lib_Model_User… in order to do this, you’d probably subclass Lib_Controller_User and override the registerAction method. This definitely works, but there are a few better ways.
You could inject the model at some point before registerAction gets run. This might look something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class Lib_Controller_User extends Lib_Controller { protected $_model = null; public function __construct($model = null) { if ($model === null) { $model = new Lib_Model_User; } $this->setModel($model); } public function setModel($model) { $this->_model = $model; } public function registerAction() { if (!empty($_POST)) { $this->_model->save($_POST); } } } // In your bootstrap process $user_controller->setModel(new Custom_User_Model); |
This works fine, but I think that Zend_Plugin_Loader gives you a much nicer way of achieving this. Here is the same code refactored to use the plugin loader instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Lib_Controller_User extends Lib_Controller { protected $_modelLoader = null; public function registerAction() { if (!empty($_POST)) { $this->_getModel('user')->save($_POST); } } protected function _getModel($name) { if ($this->_modelLoader === null) { $pl = new Zend_Loader_PluginLoader; $pl->addPrefixPath('Lib_Model', 'Lib/Model/'); $pl->addPrefixPath('Custom_Model', 'Custom/Model/'); $this->_modelLoader = $pl; } $model = $this->_modelLoader->load(ucfirst($name)); return new $model; } } |
This will first look to see if Custom_Model_User exists, if it’s not found, it will load Lib_Model_User instead. It lazy loads your models using the order you specify. This makes your controller a lot more reusable and cuts down on the amount of bootstrap code you need. Note, if you wanted to use this in a real world scenario, you’d probably want to specify your custom loader paths through configuration instead.
Zend Framework makes use of plugin loaders quite a bit. Zend_Form is a great example because it allows you to specify where to load elements when using the available factory methods like Zend_Form::createElement and Zend_Form::addElement. Doing this makes code much more flexible, I like the approach.
Zend Framework PEAR Channel — update
My last post talked about the available Zend Framework PEAR channels out there. I’ve been using one to grab our Zend Framework installs for work and it’s worked well. My only complaint is that the PEAR version is lagging behind the currently released ZF version. Tonight I thought I would take a look at the channel I decided not to use and see where it stood. It is in fact up to date with 1.7.7 where as the one I’m using is at 1.7.4. I guess it’s sort of a non-issue as it’s easy to switch to another channel.
Regardless, here is a breakdown of those two channels and their current ZF versions:
| PEAR Channel | Website | Current ZF Version |
| pear.zfcampus.org | Blog post explaining channel | 1.7.4 |
| zend.googlecode.com/svn | Google Code Project | 1.7.7 |
Do you know of any other PEAR channels offering Zend Framework?
Zend Framework PEAR Channel
A while back I tried to find a Zend Framework PEAR channel, but was unsuccessful. Reading through the December issue of PHP|architect I was pumped that someone has been kind enough to start maintaining one. It is available here:
http://code.google.com/p/zend/
Update: After posting this to twitter, Matthew Weier O’Phinney let me know that there is another ZF PEAR channel setup. This one includes development versions, alphas, betas, rc’s etc. Check it out here:
http://ralphschindler.com/2009/01/07/the-semi-official-zend-framework-pear-channel
Validating URLs with Zend Framework
It’s pretty common to want to validate a URL when processing a form. The Zend Framework has a lot of validators that can be used out of the box with Zend_Form. Unfortunately, there is no Zend_Validate_Uri. Upon closer inspection, you will find Zend_Uri, which indeed can be used to check for a valid URL; however, it does not implement the Zend_Validate_Interface and cannot be used as a drop-in form element validator. Fortunately, it is pretty easy to come up with something that can be dropped in:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Url_Validator extends Zend_Validate_Abstract { const INVALID_URL = 'invalidUrl'; protected $_messageTemplates = array( self::INVALID_URL => "'%value%' is not a valid URL.", ); public function isValid($value) { $valueString = (string) $value; $this->_setValue($valueString); if (!Zend_Uri::check($value)) { $this->_error(self::INVALID_URL); return false; } return true; } } // When creating the form: $website = $form->createElement('text', 'website'); $website->addValidator(new Url_Validator); |
This reminds me of a senior coder I used to work under who constantly yelled at us to “Code to the interface!”.
Note: I originally posted this as a response to a thread here.