LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Friday, April 3. 2009

The simplest Zend_Form decorator

I've been seeing ranting and general confusion about Zend_Form decorators (as well as the occasional praises), and thought I'd do a mini-series of blog posts showing how they work.

First, some background on the Decorator design pattern. One common technique is to define a common interface that both your originating object and decorator will implement; your decorator than accepts the originating object as a dependency, and will either proxy to it or override its methods. Let's put that into code to make it more easily understood:


interface Window
{
    public function isOpen();
    public function open();
    public function close();
}

class StandardWindow implements Window
{
    protected $_open = false;

    public function isOpen()
    {
        return $this->_open;
    }

    public function open()
    {
        if (!$this->_open) {
            $this->_open = true;
        }
    }

    public function close()
    {
        if ($this->_open) {
            $this->_open = false;
        }
    }
}

class LockedWindow implements Window
{
    protected $_window;

    public function __construct(Window $window)
    {
        $this->_window = $window;
        $this->_window->close();
    }

    public function isOpen()
    {
        return false;
    }

    public function open()
    {
        throw new Exception('Cannot open locked windows');
    }

    public function close()
    {
        $this->_window->close();
    }
}
 

You then create an object of type StandardWindow, pass it to the constructor of LockedWindow, and your window instance now has different behavior. The beauty is that you don't have to implement any sort of "locking" functionality on your standard window class -- the decorator takes care of that for you. In the meantime, you can pass your locked window around as if it were just another window.

One particular place where the decorator pattern is useful is for creating textual representations of objects. As an example, you might have a "Person" object that, by itself, has no textual representation. By using the Decorator pattern, you can create an object that will act as if it were a Person, but also provide the ability to render that Person textually.

In this particular example, we're going to use duck typing instead of an explicit interface. This allows our implementation to be a bit more flexible, while still allowing the decorator object to act exactly as if it were a Person object.


class Person
{
    public function setFirstName($name) {}
    public function getFirstName() {}
    public function setLastName($name) {}
    public function getLastName() {}
    public function setTitle($title) {}
    public function getTitle() {}
}

class TextPerson
{
    protected $_person;

    public function __construct(Person $person)
    {
        $this->_person = $person;
    }

    public function __call($method, $args)
    {
        if (!method_exists($this->_person, $method)) {
            throw new Exception('Invalid method called on TextPerson: ' .  $method);
        }
        return call_user_func_array(array($this->_person, $method), $args);
    }

    public function __toString()
    {
        return $this->_person->getTitle() . ' '
               . $this->_person->getFirstName() . ' '
               . $this->_person->getLastName();
    }
}
 

In this example, you pass your Person instance to the TextPerson constructor. By using method overloading, you are able to continue to call all the methods of Person -- to set the first name, last name, or title -- but you also now gain a string representation via the __toString() method.

This latter example is getting close to how Zend_Form decorators work. The key difference is that instead of a decorator wrapping the element, the element has one or more decorators attached to it that it then injects itself into in order to render. The decorator then can access the element's methods and properties in order to create a representation of the element -- or a subset of it.

Zend_Form decorators all implement a common interface, Zend_Form_Decorator_Interface. That interface provides the ability to set decorator-specific options, register and retrieve the element, and render. A base decorator, Zend_Form_Decorator_Abstract, provides most of the functionality you will ever need, with the exception of the rendering logic.

Let's consider a situation where we simply want to render an element as a standard form text input with a label. We won't worry about error handling or whether or not the element should be wrapped within other tags for now -- just the basics. Such a decorator might look like this:


class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract
{
    protected $_format = '<label for="%s">%s</label><input id="%s" name="%s" type="text" value="%s"/>';

    public function render($content)
    {
        $element = $this->getElement();
        $name    = htmlentities($element->getFullyQualifiedName());
        $label   = htmlentities($element->getLabel());
        $id      = htmlentities($element->getId());
        $value   = htmlentities($element->getValue());

        $markup  = sprintf($this->_format, $id, $label, $id, $name, $value);
        return $markup;
    }
}
 

Let's create an element that uses this decorator:


$decorator = new My_Decorator_SimpleInput();
$element   = new Zend_Form_Element('foo', array(
    'label'      => 'Foo',
    'belongsTo'  => 'bar',
    'value'      => 'test',
    'decorators' => array($decorator),
));
 

Rendering this element results in the following markup:

<label for="bar-foo">Foo</label><input id="bar-foo" name="bar[foo]" type="text" value="test"/>

You could also put this class in your library somewhere, inform your element of that path, and refer to the decorator as simply "SimpleInput" as well:


$element = new Zend_Form_Element('foo', array(
    'label'      => 'Foo',
    'belongsTo'  => 'bar',
    'value'      => 'test',
    'prefixPath' => array('decorator' => array(
        'My_Decorator' => 'path/to/decorators/',
    )),
    'decorators' => array('SimpleInput'),
));
 

This gives you the benefit of re-use in other projects, and also opens the door for providing alternate implementations of that decorator later (a topic for another post).

Hopefully, the above overview of the decorator pattern and this simple example will shed some light on how you can begin writing decorators. I'll be writing additional posts in the coming weeks showing how to leverage decorators to build more complex markup, and will update this post to link to them as they are written.

Update: Fixed text in thrown exception to reflect actual class name; updated label generation to use id for "for" attribute, per comment from David.

Also in this series:

  • From the inside out: How to layer decorators
Posted by Matthew Weier O'Phinney in PHP at 08:30 | Comments (37) | Trackback (1)
Defined tags for this entry: decorators, php, zend framework
Related entries by tags:
Module Bootstraps in Zend Framework: Do's and Don'ts
Responding to Different Content Types in RESTful ZF Apps
Symfony Live 2010
Creating Re-Usable Zend_Application Resource Plugins
Quick Start to Zend_Application_Bootstrap

Trackbacks
Trackback specific URI for this entry

Tutorial: Formulare mit Zend_Form und Dekoratoren selbst gestalten
Zend_Form ist eine Klasse, mit der man schnell und einfach Web-Formulare zusammenbasteln kann. Man sagt einfach, welche Inputfelder man benötigt, gibt Beschriftung und Validatoren (Eingabeprüfungen) an, fertig. Um die Ausgabe als (X)HTML braucht
Weblog: Die WEB-Architektin bloggt...
Tracked: Sep 18, 04:36

Comments
Display comments as (Linear | Threaded)

Thanks Matthew for good explanation of decorators behavior.
I am used it a lot in my code and it is good example with format string.
Write more about it and about forms, especially forms with custom design.
#1 Yaroslav Vorozhko (Link) on 2009-04-03 09:08 (Reply)
Another great article, Matthew! I'd been stumped by the decorator pattern, but now I understand.
#2 Justin Woods (Link) on 2009-04-03 09:50 (Reply)
Thanks for the nice explanation. Hopefully, this series on Zend_Form_Decorator teaches ZF beginners to have fun while coding. :-)
#3 Sudheer (Link) on 2009-04-03 09:52 (Reply)
now this is what I call a meaty blog post
#4 Ariel Arjona on 2009-04-03 09:58 (Reply)
for further reading:
good articles about zend_form, especially about the decorators of zend_form can be found here:
http://codeutopia.net/blog/2008/08/07/zend_form-decorator-tips/
#5 uli on 2009-04-03 12:54 (Reply)
Thanks Matthew. Will your series cover usage of Zend_Form _without_ Zend_View?
For example with a template engine like Dwoo. What we're missing from the documentation is the possibility to give our html team more control of _where_ to place single form elements - or can they only be rendered in a single block? Is it possible to use Zend_Form in a way that the ArrayRenderer or SmartyRenderer from QuickForm worked? Yeah, old times... ;-)
We have a strict seperation from php coders and html workers, and this was really handy - they had all the split up information at hand in an array and could do whatever they wanted to do with it.

Really looking forward to the next posts.
#6 Stephan Wentz on 2009-04-03 19:34 (Reply)
Well, the example above doesn't use Zend_View. ;-)

As it is right now, if you create a view class that implements Zend_View_Interface, you can the assign it to your form (and thus your elements), you can then pull this into your decorator ($view = $element-&gt;getView(), and do whatever you want with the view -- and this is what we do with the standard decorators (which then use view helpers).

If you pay close attention to the above examples, you should see ways you can start proxying to other functionality via your decorators to render.
#6.1 Matthew Weier O'Phinney (Link) on 2009-04-03 21:33 (Reply)
Is there an example for this in the docs somewhere? That's just what we need - but we didn't get it running. What do I assign to the view? The decorator? And how do I output it in the view?
#6.1.1 Stephan Wentz on 2009-04-04 04:33 (Reply)
You assign the form to the view, and simply echo it or call its render() method. Or, in the case that you're using a ViewScript decorator, you might echo or render() the individual elements. (Echoing calls the __toString() method, which simply proxies to render().)

render() then iterates through the attached decorators in order to generate the final output.
#6.1.1.1 Matthew Weier O'Phinney (Link) on 2009-04-04 07:39 (Reply)
I usualy create forms this way

protected $_format = '%s';

Could you comment a bit if that's a good/bad way of using labels?
#7 Stuardo -StR- Rodríguez (Link) on 2009-04-03 20:39 (Reply)
hmm.. your blog didn't turn them into htmlentities :-P

protected $_format = '[label][span]%s[/span][input name="%s" value="%s"/][/label]';
#7.1 Stuardo -StR- Rodríguez (Link) on 2009-04-03 20:42 (Reply)
Labels are meant to semantically tie one piece of content to another -- the label to the input. What you're doing here is introducing a tag that has no semantic meaning (the span tag); its only purpose is for display. The other issue here is that this approach makes it harder to put the label _following_ the input. If you separate the tags and have simply a label and an input, you can use CSS to position them appropriately.
#7.1.1 Matthew Weier O'Phinney (Link) on 2009-04-03 21:36 (Reply)
That's a perfectly legit way of using labels. It will implicitly associate the label with the input (don't need to use the for attribute). The drawback is that it can only contain one control element. Which means you can't do:
[label]Date:
[input type="text" name="day"]
[input type="text" name="month"]
[input type="text" name="year"]
[/label]

Of course it might still work in most browsers, but is technically wrong according to the specs.

@Matthew:
The for attribute of the label should contain value of the id, not the name.
http://www.w3.org/TR/html401/interact/forms.html#adef-for

Great write-up though. Really makes it easy to grasp the concept.

The exception being thrown in TextPerson refers to HtmlPerson. Typo? :-)
#7.1.2 David (Link) on 2009-04-03 22:05 (Reply)
so, if I would like toi be "technically correct" and use labels at the same time, I'm forced to use the "for" attribute in the label tag, and the id attribute in each input tag?
#7.1.2.1 Stuardo -StR- Rodríguez (Link) on 2009-04-04 00:15 (Reply)
Pretty much. But as Matthew pointed, having the two separated makes it more flexible in terms of styling. As for having a span element within the label it can be handy to act as a hook if you need to update the value using javascript, but usually can and should be left out. That said, you may want to include a span within the label for displaying errors related to the label's control element, as this aids accessibility for those using screen readers (or so I've been told...).
#7.1.2.1.1 David (Link) on 2009-04-04 08:32 (Reply)
I've updated the code to reflect both of your points -- thanks!
#7.1.2.2 Matthew Weier O'Phinney (Link) on 2009-04-04 07:33 (Reply)
Your label's for attribute should reference to an ID, not the name of the input.

The reason behind this becomes obvious and clear when you have a couple of radio buttons, each has it's own label but they all have the same name.
#8 Harro on 2009-04-04 07:31 (Reply)
I've updated this in the code at this time.
#8.1 Matthew Weier O'Phinney (Link) on 2009-04-04 07:34 (Reply)
You forgot to update the rendered result.
#8.1.1 David (Link) on 2009-04-04 08:17 (Reply)
Fixed, thanks.
#8.1.1.1 Matthew Weier O'Phinney (Link) on 2009-04-04 11:38 (Reply)
This article is welcome, however if anything it shows that it still takes "alot" of work to set up and use what amounts to simply marking up form elements. The whole thing would be much simpler if it were just a matter of giving us a raw prefix and suffix to each form element and nixing the whole decorator pattern.

All we really need to do is put text before and after a form element, without all the extra work the decorator patters do "for" us. Period. At least build in that simplistic option for those of us not impressed by form decorators. With all due respect, it's clunky and over bloated.
#9 L. Long on 2009-04-04 15:16 (Reply)
We will have to agree to disagree. If what you call "alot" of work is 7 or 8 lines of code, then I would have to say that you probably are asking for too much magic behind the scenes.

The decorator above is very simple in concept and in implementation -- and the pattern offers the most flexibility for a problem area that demands a ton of flexibility. Basically every developer and designer I've ever met has had a different idea of how form markup should be done, and the experienced ones among them have all developed standard solutions that they use over and over again. Decorators give the flexibility to implement any sort of markup you desire, and make it repeatable. Is it bloated? Not really; the actual implementation of the pattern within the form component is maybe a dozen lines. Is it clunky? That really depends on how complex the markup you're generating is, and how you're mixing together your decorators. I'd argue that if you have complex markup, you should be creating your own decorators to simplify the process rather than mixing many decorators together.

If you want to do simply output text before and/or after an element, that's already trivially easy. Either use the ViewScript decorator, or simply pull elements individually from the form and render only their ViewHelper renderer (echo $form->elementName->renderViewHelper()), and use the metadata in the element to display other artifacts (the label, for instance, or validation error messages).

I'll be going over some of these techniques in this series. Decorators make markup repeatable; having the elements encapsulate their own metadata makes any implementation you desire possible.
#9.1 Matthew Weier O'Phinney (Link) on 2009-04-04 22:33 (Reply)
Also helps separate concerns. The form shouldn't have to know what format it'll be output to. It'll probably be HTML or XHTML, but could also be XUL or GLIDE or GTK widgets. (unless I've missed something :-))
I can't see how using a "prefix/suffix" solution would be that flexible.

@Matt, I think I found another error in your decorator. You aren't doing anything with the content being sent to the decorator. Aren't you meant to return it together with the generated markup?

return $content . $markup;
#9.1.1 David (Link) on 2009-04-05 02:12 (Reply)
That's the subject of the next post. :-)
#9.1.1.1 Matthew Weier O'Phinney (Link) on 2009-04-05 08:54 (Reply)
What I would like for Zend_Form is the options to set the default decorators for the forms/groups and elements. Now I have to override almost the whole of zend_form to get the form layouts to a default I'd like.

simply defining the defaults in a protected variable and then overwriting them with a passed zend_config (and passing the element's defaults automatically when you add them) would make me a lot happier.
#10 Harro on 2009-04-05 07:41 (Reply)
Umm... that's almost precisely how they currently work. A method, loadDefaultDecorators(), sets up the default decorators for an element; if any are passed during initialization (or by calling setDefaultDecorators()), then those will be used instead.
#10.1 Matthew Weier O'Phinney (Link) on 2009-04-05 08:56 (Reply)
Maybe I'm doing something wrong, but instead of the whole dd dt dl wrappers I want to wrap the the elements with a table, each element with a tr and then the label and items with a td.
The field groups should be grouped using a tbody.

Now I can set the elementDecorators on the form using a config, but then buttons also get the same decorators, while I might not want that.

But.. here comes the thing, when adding the elements in the init method I want to be able to override those defaults like I can currently override them.

Currently each element and the form has the decorators hardcoded in the loadDefaultDecorators function.. I'm working with the latest release here so unless that's changed in trunk or something I don't see how I can get the desired behaviour without overriding the whole of Zend_Form.

I just want to change the default decorators, without losing the option to override them when adding the elements.
#10.1.1 Harro on 2009-04-06 14:27 (Reply)
This is a question that's better asked on-list where I can help you out asynchronously; it really has nothing to do with this blog post. You can find the mailing lists at http://framework.zend.com/archives -- choose the fw-mvc list.
#10.1.1.1 Matthew Weier O'Phinney (Link) on 2009-04-06 14:37 (Reply)
True, I'll see if I can write a coherent story this evening.
#10.1.1.1.1 Harro on 2009-04-07 10:20 (Reply)
wow thats great info

thanx alot dear
#11 Asheerq (Link) on 2009-04-05 23:26 (Reply)
Nice article, simple examples. Thank you very much!
#12 Yaroslav Shatkevich on 2009-04-07 03:11 (Reply)
thanks a lot Matthew .. reuse component is very useful especially for large project. this tutorial is simpler than reading the full complete tutorial about decorator..
#13 Riki Risnandar (Link) on 2009-07-10 07:04 (Reply)
If a majority of users are having to extend the decorators, Then why even have the defaults. or better yet a simple decorator default function set in the bootstrap or config file. Most websites are based on forms and a majority of the websites I have seen do not render their forms at all like the default decorators. While it is true that the decorators are versatile They do much more than they should. I've had to extend them myself and I see no problem with this. I think it would be more beneficial if the 7 or 8 lines of extra code would be in the view file for the form, but that is just a thought. It is just I cannot understand why the defaults are set like they are. Here is my question, how was it decided to use dt and dd tags as a default? The reason I am asking is because these tags inherit default placements, an explanation for the element can always be used in the title attribute of the tag or in the view file. Most forms usually follow a left right rule, label on the left input on the right, while the default tags do not. While patterns are nice on paper, sometimes they lose they authority in the real world, especially if they are misunderstood.
#14 dave on 2009-09-21 21:47 (Reply)
Listen, the decision about the base decorators to use was one made not in isolation, but with the input of quite a number of contributors and testers. DL/DT/DD was chosen because it provides a highly semantic markup that is trivial to style using CSS to allow both LTR and RTL markups.

I actually see very few people extending the decorators; typically, they select the few decorators they need, write decorators that create the exact markup they need, or use selective decorators from within the view in order to create highly customized markup.

Please, when stating opinions like yours, do not make the assumption that you know all the ins and outs of a particular development process. I have been over the design and decisions of Zend_Form many, many times, and development of the component was completely in the open. It is the product of many man-hours of both myself, as well as numerous other contributors and testers, and the decisions made, while perhaps not readily apparent, are sound.
#14.1 Matthew Weier O'Phinney (Link) on 2009-09-21 22:37 (Reply)
Well my friend you have met the man who knows all the ins and outs. (#TODO Insert corny LOL here ). Matt basically Have you ever written code and then doubt sets in? Don't take this personally, Zend Form Decorators are the only part of the framework I question as having existence in the framework, if I am wrong than I am wrong, no big deal. But that is my personal opinion and should be taken as such, grain of salt. No big deal. I appreciate that you took the time to reply and take the time to explain certain aspects of the framework.

I was wondering if their will ever be a format on this blog where you ask a question and see the what kind of replies you get back. Like how would I go about making a form with zend framework or How would I interact with the acl. (I couldn't do this because people would just think I was a lazy programmer, and that might be the case). I find when teaching that asking a question and then getting replies makes the learning process that much easier for both parties. If I taught this is how it is done so shall it be, then the replies get convoluted (well maybe my replies).
If I could take your time for one more second ask me a question and then you will know which ins and outs I do know (please no Babbage questions).
If you feel that this is a waste of your time than it is also a waste of mine.
well I already lost three grains of salt so any how have a good one.
East Coast Trolling Ball-Busters Union #8907
#14.1.1 dave on 2009-09-22 18:46 (Reply)
I always second-guess my code. But I also run ideas by a number of people on a regular basis -- close friends, other ZF contributors, etc.

Zend_Form had *many* goals, and one of the ones that was most desired by those commenting on the proposals we had in play was that forms be able to represent themselves -- in other words, that they be able to render themselves. The question was then: how can we do so in a flexible and extensible way. The only answer that had any merit was a decorator system. Decorators are used by quite a number of libraries to do exactly what we have them do: represent a form as markup. The details on how they work differ immensely.

I'd ask what "ins and outs" you know, but I don't think that's a conversation for blog comments. Why don't you write up a critique of Zend_Form on your blog, pointing out what you'd do differently, and link a trackback to this post? That would get information out to more people, and could start a much better dialog. :-)
#14.1.1.1 Matthew Weier O'Phinney (Link) on 2009-09-22 18:59 (Reply)
thats great info

thanx alot dear
#15 3sheerq (Link) on 2010-02-16 15:22 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back March '10 Forward
Mon Tue Wed Thu Fri Sat Sun
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 28
29 30 31        

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

March 2010
February 2010
January 2010
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml rest
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net