Zend_Form, default decorators and fieldsets

Another less-sysadmin-y, more code-y post today.

Zend_Form is pretty handy, and takes care of a lot of the hard work in producing and validating forms. Unfortunately the default decorators aren't quite as sane in my opinion, which becomes obvious if you start using fieldsets, or display groups, as ZF refers to them - You'll see your fieldsets getting wrapped in an additional definition list which is basically crap if you ask me. You can get rid of them with CSS, but its tricky.

To avoid this, until today I've been using a set of custom decorators and getting increasing frustrated in having to add more and more to support ZendX_JQuery_Element's and Zend_Form_Element_File, etc. Feeling somewhat defeated as I was hitting the limits of the decorators documentation, I started taking another look at the default decorators and what could be done with the minimal amount of tedious work, in terms of drop in replacements and slight CSS alteration. I came up with a reasonably good solution that I cannot believe I didn't see before.

I've plonked the code below (sanitized from anything specific to my setup - such as HTMLPurifier integration, etc. - with a test form to help you see how it works) incase anyone happens to be interested. Given the unusual increase in commenting I've been getting against using Zend Framework and the PHP Sqlsrv extension I figured it might be worth posting (I'm sure I'm not the only person out there who has missed the obvious).

What I've basically done is tell the form to render form elements, wrapped in a form. Display groups should then only display the elements, wrapped in a dl, wrapped in the fieldset. I've added some "plain" fieldsets which I use for control elements, such as submit or reset buttons. I've also given these items classes which allows me to remove the fieldset through styling, and move the positioning easily.
class My_Form extends Zend_Form { // Form protected $_formDecorator = array('FormElements', 'Form'); // Display Groups protected $_groupDecoratorStd = array('FormElements', array('HtmlTag', array('tag'=> 'dl')), 'Fieldset'); protected $_groupDecoratorCtl = array('FormElements', 'Fieldset'); // Ctls and hidden elements protected $_elementDecoratorCtl = array('ViewHelper'); public function __construct($options = null) { parent::__construct($options); // Add our HTMLPurifier filter(s) $this->addElementPrefixPath('My_Filter', 'my/Filter/', 'filter'); // Add our Confirmation validator $this->addElementPrefixPath('My_Validate', 'my/Validate/', 'validate'); // Add our custom elements path $this->addPrefixPath('My_Form_Element', 'my/Form/Element/', Zend_Form::ELEMENT); $this->setAttrib('accept-charset', 'UTF-8'); $this->setMethod('post'); $this->setDecorators($this->_formDecorator); $this->setDisplayGroupDecorators($this->_groupDecoratorStd); } public function addAntiCSRF($salt, $timeout = 300, $name = 'no_csrf_foo') { $this->addElement('hash', $name, array( 'decorators' => $this->_elementDecoratorCtl, 'salt' => $salt, 'timeout' => $timeout )); } public function addSubmit($labelSubmit = 'Submit') { $this->addElement('submit', 'submit', array( 'label' => $labelSubmit, 'decorators' => $this->_elementDecoratorCtl, 'class' => 'submit' )); $this->addDisplayGroup( array('submit'), $this->getGroupName('controls'), array( 'legend' => 'Controls', 'class' => $this->getControlsClass(), 'decorators' => $this->_groupDecoratorCtl ) ); } public function addSubmitReset($labelSubmit = 'Submit', $labelReset = 'Reset') { $this->addElement('submit', 'submit', array( 'label' => $labelSubmit, 'decorators' => $this->_elementDecoratorCtl, 'class' => 'submit' )); $this->addElement('reset', 'reset', array( 'label' => $labelReset, 'decorators' => $this->_elementDecoratorCtl, 'class' => 'reset' )); $this->addDisplayGroup( array('submit', 'reset'), $this->getGroupName('controls'), array( 'legend' => 'Controls', 'class' => $this->getControlsClass(), 'decorators' => $this->_groupDecoratorCtl ) ); } protected function getGroupName($name) { return get_class($this).'-'.$name; } protected function getControlsClass() { return array('controls', get_class($this).'-controls'); } protected function getInputsClass() { return array('controls', get_class($this).'-inputs'); } } class Test_Form extends My_Form { public function init() { $this->addElement('text', 'title', array( 'label' => 'Title', 'validators' => array( array('StringLength', false, array(2,50)) ), )); $this->addElement('file', 'file', array( 'label' => 'file', )); $elem = new ZendX_JQuery_Form_Element_DatePicker( 'dp', array( "label" => "Date Picker", 'validators' => array('Date'), 'required' => true ) ); $this->addElement($elem); $this->addElement( 'hidden', 'shush', array( 'value' => 'its quiet', 'decorators' => $this->_elementDecoratorCtl ) ); $this->addElement('text', 'comments', array( 'label' => 'Comments', 'validators' => array( array('StringLength', false, array(2,50)) ), )); $this->addDisplayGroup( array('title', 'file', 'dp', 'comments'), 'inputs', array( 'legend' => 'INputs' ) ); $this->addSubmitReset(); } }
Granted there are other solutions, but this works rather well for what I needed - a drop in replacement and the (valid) HTML structure that I wanted.

You should probably want to alter this if you're starting without any legacy stuff hanging over your head.

It should be noted this was written against Zend Framework 1.9.2, there's no guarantee that this will still be valid against future versions or older versions (although it's pretty likely and I've been using Zend_Form with few changes since early 1.x).

Zend Framework and the PHP Sqlsrv extension

On the off chance you're intending to do any development with using SqlSrv and ZF, I'd suggest taking a cursory look at ZF-7431 before hand. Equally if you're planning on doing any dev with Sqlsrv and plan to migrate to other SQL platforms later, then it could be just as helpful.

The fact that SqlSrv will return PHP objects is rather nice, unless you already have existing code that assumes strings are returned, like almost all other database extensions available for PHP. The easiest "fix" to allow your code to work across as many systems as possible is to ensure that you pass in ReturnDatesAsStrings as an option.

Against better judgement

I've been playing around with CouchDB for a few nights, inspired by the work of Stuart Langridge and others at Ubuntu, and also J Chris Anderson.

To break myself into the CouchDB world I started poking around at the capabilities, and mostly trying to not think of SQL-isms. Understanding map/reduce and getting your brain out of the SQL world is worth it, if for no other reason than to get a different perspective on data storage.

Unfortunately I decided, rather than write what I really thought would be interesting (a Thunderbird provider for couchdb, so that I can replicate my lightning calendar and contacts to my server, laptop and desktop, without using SyncKolab[1]), that it would be best if I started simple.

I chose to develop a pure CouchDB application, using the rather nice couchapp. I write web apps and this should be a gentle introduction, and fairly quick (which is what I wanted primarily). Really, How hard could it be? So I pulled down couchapp, did a bit of reading and built a VM to run couchdb 0.9, as several newer features are required than the current version in Debian stable. What I'd failed to realise at this point was that the "API" for pure couch applications are a bit in flux. After a few hours, over the course of a few evenings I've become somewhat frustrated, until I noticed a page entitled Formatting with Show and List on the CouchDB wiki tonight. A lot of the available code out there uses the new API, which explained why a hell of a lot made bugger all sense and why I'd almost started pulling down the couchdb 0.9 to have a butchers.

Now this isn't a slur on anyone except me. I was so blinkered after the joy of understanding why non-SQL databases do have a place in the universe that I failed to search the wiki correctly.

To give the whole post a sysadmin-style slant, during this I'd started noticing the CouchDB growing quickly. Now granted I was doing a lot of pushing of attachments, shows and lists, but the growth seemed rather unproportioned. After doing some tests of my own I hit the web and found that Joan Touzet has some interesting thoughts on the subject as well, which you should go and read now. Naturally without putting my tests into the real world don't take it as gospel, but if you're using couchdb you might want to ensure that you're doing things the right way, lest your sysadmin smite you with the +2 damage hammer of server resources.

I'm trying very hard not to paint a bad picture of CouchDB here. Genuinely I think it's a good project, and although it certainly has a lot of competition, it does seem to have a lot of mindshare. There are also good points to, for one, backups are easy.

[1] Not that I have a problem with SyncKolab, it's serving me well, and probably will continue to do so.

Not your usual 0-day?

My feed reader just picked up a new article over at ISC about a new 0-day for Linux. You should go and read it now, because it's fairly different from most other exploits and comes from fairly innocuous looking code.

Not being a real programmer (I'll never call myself a real programmer - I hack together code, but it's almost never elegant) things like this always fascinate me.

Two things...

Hoorah, and oh knackers.

jQuery 1.3 is out, but I've nearly finished a relatively long project at work involving jQuery (in part). I was kinda hoping to get back to hardcore sysadmin'ing, but I fear this may drag me back to development for a little bit, due to both inspiration and a few little bug fixes.