Page 1 of 1

Painters and Decorators

Posted: Fri Dec 22, 2006 9:25 pm
by Ollie Saunders
For those of you that don't know I'm currently writing a forms library. In the library there are "entities" such as fieldsets, fields or even user friendly text instructions. Obviously in a library you have quite a few different types of these and I've been using lots (too much) inheritance.

Now the idea was that this wouldn't be such a big problem as it was in previous incarnations of my library because now I have separated out all the rendering (some people call these painters hence subject of topic) functionality out into separate objects using composition! I've used this technique a lot and its proven very effective. I'm able to keep the roles of my classes more tightly focused and I can even be swap out renderers. I get low coupling and high cohesion. This is the strategy pattern.

Unfortunately not everything can be done this way and so I've still had to use a fair bit of inline functionality. Here's one example. I've got:
  • Entity - Compositions for unique identification, rendering and setting of visual attributes
  • Widget extends Entity - Composition for getting input (retriever) and inline functionality for attaching/dispatching events, adding errors, validation shorthand, setting initial value
  • Text extends Widget - Inline functionality for input filtering
And I think to myself OK this is good I've got a complete text widget it has all the functionality it needs and no more, there's a far bit of inheritance but it's acceptable as there's no redundancy or duplication.

So next I do this:
  • Entity - as before
  • Widget extends Entity - as before
  • Select extends Widget - inline functionality add options to the select
and again I think OK, that's cool. And then I think "Ahh it might be useful to have input filtering on the Select"...err well I would have to:
  • separate the filtering I wrote for Text
  • into a separate class that can interact with most widget
  • use composition to get it into both Select and Text
Unfortunately both class still require functions to set and get the filterer...so there is duplication but it's no biggy.

I even managed to do this:
  • Entity
  • Widget extends Entity
  • Container extends Widget - implementation of composite pattern (577 lines here :twisted:)
  • CheckBox_Container extends Container - a checkbox which you can store other widgets in but they are only processed when the checkbox is checked
Now there is a lot of inheritance and, true to the words of the wise there were complications with this much inheritance, I had to use this:

Code: Select all

class Container extends Widget {
    protected function _hasValue()
    {
        return false;
    }
This method is there to allow classes that extend container to also still function as widgets as well but only when its overridden to true! Unlike the strategy stuff this isn't a pattern; its a hack and a dirty one at that but it did its job.

I can see myself needing to do this kind of thing more and more and its beginning to get complicated and difficult to maintain. Actually a lot of the places where I've used the composition and the strategy pattern I'm creating new interfaces between classes that are tricky to work with --- In order to get a Filterer that will work with Text and Select I've got to get a value to Filterer at a time when it should only be available the widget itself. Accommodating new features in this way makes superclasses such as Widget and Container into finely tuned, fragile, pieces of class simply for the benefit of their subclasses and they represent a maintenance problem.

Yesterday I realised I had a bug where a form could be spoofed with certain request variables missing and the validations for those variables wouldn't be run. Those validations include those that enforce the presence of values. This is a pretty serious security issue and I realised there was really no way of fixing it apart from a couple of scarily fundamental changes involving.....decorators.

I'm not a fan of decorators. I've tried to use them three times in the past and each time I've discovered it was simpler to do without them. A lot of books give you examples of decorators that can be written more easily without them, using boolean flags or arrays instead. I understood their value and the problems with inheritance but I told myself this time the strategy would save me. To be fair until recently it has worked very well and I had lots of reason why I didn't want to use decorators:
  • Lots of small classes make library harder to understand
  • Can't decorate public properties so lots more setters and getters
  • My widgets don't need to change at runtime and that seems to be the main point of decorators
  • I really dislike those decorator classes full of methods that just chain on to the object they are decorating
  • Inferior performance
It would seem now...so close to the end all of those reasons counted for very little and that crucial decision I made at the beginning was wrong. Cue sad face: :(

Right so I'm going to have to use decorators. A forth rewrite is a depressing prospect but thinking about it I'm actually quite excited about how its going to remove all those difficult complications. So...and here is the reason for this post...I thought about a new design involving decorators. Instead of this
  • Entity - Compositions for unique identification, rendering and setting of visual attributes
  • Widget extends Entity - Composition for getting input (retriever) and inline functionality for attaching/dispatching events, adding errors, validation shorthand, setting initial value
  • Text extends Widget - Inline functionality for input filtering
I can do something like this
  • Entity - Compositions for unique identification, rendering and setting of visual attributes
  • EntityDecorator extends Entity - for decorators
  • Filtering extend EntityDecorator
  • Events extend EntityDecorator
  • Errors extend EntityDecorator
  • Retrieving extend EntityDecorator
One problem I can instantly see is that Events functionality will need to talk to Retrieving functionality and so that obviously isn't going to work.

So I tried a prototype of something really simple. A widget that is decorated to become a proper select field. The prototype still has a separate objects for rendering. The code is quite long but simple:

Code: Select all

<?php
/**
 * To ensure consistency between the decorator and decorated
 */
interface Widget_Interface {
    function getId();
    function setId($newId);
    function render();
    function getLabel();
    function setLabel($newLabel);
    function getRenderer();
    function setRenderer(Renderer_Interface $newRenderer);
}
class Widget implements Widget_Interface {
    private $_id, $_label, $_renderer;
    public function __construct($id) {
        $this->setId($id);
    }
    public function getId() {
        return $this->_id;
    }
    public function setId($newId) {
        return $this->_id = $newId;
    }
    public function render() {
        return $this->_renderer->render($this->_getTransferForRenderer());
    }
    public function getLabel() {
        return $this->_label;
    }
    public function setLabel($newLabel) {
        return $this->_label = $newLabel;
    }
    public function getRenderer() {
        return $this->_renderer;
    }
    public function setRenderer(Renderer_Interface $newRenderer) {
        return $this->_renderer = $newRenderer;
    }
    protected function _getTransferForRenderer() {
        // send renderer the stuff it needs
        $obj = new stdClass();
        $obj->id = $this->getId();
        $obj->label = $this->getLabel();
        return $obj;
    }
}
interface Renderer_Interface {
    function render(stdClass $transferObj);
}
abstract class Renderer implements Renderer_Interface {
    protected $_trans;
    // Implementation of template method pattern
    final public function render(stdClass $transferObj) {
        $this->_trans = $transferObj;
        return $this->_renderHead()
             . $this->_renderContent()
             . $this->_renderFoot();
    }
    protected function _renderHead() {
        $t = $this->_trans;
        return '<div class="field"><label for="' . $t->id . '">' . $t->label . '</label>';
    }
    abstract protected function _renderContent();
    protected function _renderFoot() {
        return '</div>';
    }
}
class Renderer_Select extends Renderer {
    protected function _renderContent() {
        $t = $this->_trans; // $this->_trans already assigned by Renderer::render()
        $out = '<select id="' . $t->id . '" name="' . $t->id . '">';
        if (!empty($t->options)) {
            foreach ($t->options as $value => $label) {
                $out.= '<option value="' . $value . '">' . $label . '</option>';
            }
        }
        return $out . '</select>';
    }
}
class Widget_Decorator implements Widget_Interface {
    protected $_widget;
    public function __construct(Widget_Interface $widget) {
        $this->_widget = $widget;
    }
    public function getId() {
        return $this->_widget->{__FUNCTION__};
    }
    public function setId($newId) {
        return $this->_widget->{__FUNCTION__}($newId);
    }
    public function render() {
        return $this->_widget->{__FUNCTION__}();
    }
    public function getLabel() {
        return $this->_widget->{__FUNCTION__};
    }
    public function setLabel($newLabel) {
        return $this->_widget->{__FUNCTION__}($newLabel);
    }
    public function getRenderer() {
        return $this->_widget->{__FUNCTION__};
    }
    public function setRenderer(Renderer_Interface $newRenderer) {
        return $this->_widget->{__FUNCTION__}($newRenderer);
    }
}
class Select extends Widget_Decorator {
    private $_options;
    public function addOption($value, $label) {
        $this->_options[$value] = $label;
    }
    protected function _getTransferForRenderer() {
        $obj = $this->_widget->{__FUNCTION__}();
        $obj->options = $this->_options;
        return $obj;
    }
}
class Factory {
    public function mkSelect($id, $label) {
        $widget = new Select(new Widget($id));
        $widget->setLabel($label);
        $widget->setRenderer(new Renderer_Select());
        return $widget;
    }
}
$factory = new Factory();
$widget = $factory->mkSelect('foo', 'Foo');
$widget->addOption('cat', 'cat');
$widget->addOption('dog', 'dog');
// I supposed to produce a select with a label of Foo id of foo and cat and dog as options
echo $widget->render();
// In reality: <div class="field"><label for="foo">Foo</label><select id="foo" name="foo"></select></div>
The way rendering works is for a widget (in this case select) to pass a transfer object to the renderer which in turn uses it to render the widget. Unfortunately because Select::_getTransferForRenderer() is a decorator the $this->_getTransferForRenderer() call in Widget::render() still references itself and thus isn't augmented by the decorator making the decorator completely smurfing useless!

So I put it to you great members of the great devnet forum board...what should I do? Clearly I'm going to have to mix real inheritance with decoration but where? and how? and is there a better solution? Maybe I just want you to ask me lots of difficult questions to get me thinking. Maybe I just want some sympathy.

Posted: Sat Dec 23, 2006 11:53 am
by Christopher
Wow wei that's a lot to digest! I really don't understand how the thing works except in a very general sense. I can tell you from my experience -- probably not what you want to hear -- that I have gone down path of building a super form manager and abandoned it. I currently use a form controller to manage input, filtering, validation, states -- but push as much of the output into the template and only use HTML generation code for selects, radios and checkboxes. I found that my form manager just got more and more complex and often very application specific.

Posted: Sat Dec 23, 2006 12:18 pm
by Ollie Saunders
Wow wei that's a lot to digest!
Yeah, it is. I couldn't really find any way of condensing it. Thanks for replying I think most people were scared off.
I have gone down path of building a super form manager and abandoned it.
That's not likely to happen to me. I've already spent errm I think its more than 6 months on this. I sent an email to Zend (turns out you get straight through to Andi Gutmans!) and I'm planning on being a major contributor to Zend_Form, which is still at proposal stage. Also I have decided I'm going to finish it without the decorators, so I will have a working forms library done. It just won't be very maintainable.
but push as much of the output into the template and only use HTML generation code for selects, radios and checkboxes
At the moment widgts send a transfer object (a stdClass with a bunch of properties set) to renderers. Every aspect of the form is generated for you. It is pretty easy to write your own renderers or extend existing ones. Also you can customize them with renderer attributes. Also you can inject pure HTML into the form structure with an inline object.

My renderers generate tableless HTML designed to be stylized with CSS and accessibility considerations taken into account although I'm sure there will be pressure on me to write a set of table-based renderers...that's no problem, renderers are designed to be interchangeable. I was thinking, for a bit of fun, to write a set of plain text renderers.

Posted: Sat Dec 23, 2006 12:58 pm
by Christopher
It actually looks really interesting. I would like to be able to try it if possible. Do you have a download with an example app using it? I could certainly give you more specific comments and would probably learn a lot as well.

Great to hear about Zend_Form. I contributed to the MVC redesign, but have been busy. Hopefully I can contribute more soon.

Posted: Sat Dec 23, 2006 2:37 pm
by Ollie Saunders
Do you have a download with an example app using it?
Its all bits and pieces at the moment. 408 passing tests, one very very simple smoke test, 4 out of date tutorials and no radio, select or file widgets yet only docblocks on 50% of code. Still wanna see it?

Posted: Sat Dec 23, 2006 3:01 pm
by Christopher
ole wrote:Its all bits and pieces at the moment. 408 passing tests, one very very simple smoke test, 4 out of date tutorials and no radio, select or file widgets yet only docblocks on 50% of code. Still wanna see it?
Sounds .... uhh. If it doesn't come with a pistol and single bullet then -- yes! ;)

Posted: Sat Dec 23, 2006 5:13 pm
by wei
it looks good so far, i have a few questions


1) pass a renderer object to the Widget::render($renderer) instead
of the other way around, so the widget becomes responsible
for rendering it, because as the widget gets more complicated
the rendering logic also rises in complexity. Also, how will
a hierachy be rendered (at the moment, it seems to be
rendered explicitly by writing "<div>")?

2) use a adapter for rendering if you need to render it differently,
it is like a Decorator but implicit within the Widget class (e.g. provide
decoration from the Widget class itself)

3) why does Widget have a label? does all widget have label? What if
I don't want a label?

4) why is Renderer::render() final?

5) you may also abstract out a HTMLWriter rather than explicitly writing
HTML strings in the renders, this also allows you to use may be the
HTMLPurifier class...

6) rather than rendering the <option> tag inline, may be a class to render
the options?

possible usage:

Code: Select all

$selections = new SelectWidget(array('cat'=>'cat', 'dog'=>'dog'));
echo $Selections->render(new HtmlWriter());
another things may be needed are hierachices, for this use Composite

Code: Select all

$selections = new SelectWidget();
$selections->addChild(new SelectOption('cat'=>'cat'));
$selections->addChild(new SelectOption('dog'=>'dog'));

$container = new DivWidget();
$container->setCssClass('field');
$container->addChild($selections);

echo $container->render(new HtmlWriter());
or change the way it actually renders

Code: Select all

$selections = new SelectWidget(new WAPSelectAdapter()); //decoration
...
echo $selections->render(new TextWriter());
there are a few other ways, but with all designs there are trade-offs.

Posted: Sun Dec 24, 2006 2:00 am
by Kieran Huggins
ole wrote:I think most people were scared off.
I was terrified!

Why not create and output a simple XML form definition and transform it with XSLT? That way you could separate the layers even further while reducing the amount of inheritance you need.

Also, the door would be left open to extend as much of it as you need, while choosing not to extend other parts. For example: you could include a regex validation string that an XSLT template could embed in the output for a Javascript validator. Other templates could ignore this altogether.

Also - you wouldn't be tied to a certain form control for a set of information. This would allow people to use non-standard form controls or roll their own... like a drag-and-drop cart interface rendered from a "select multiple" set.

Does that make any sense? I might be wayyy off the mark on this one!

Cheers,
Kieran

Posted: Sun Dec 24, 2006 3:28 am
by timvw
Kieran Huggins wrote:
ole wrote:I think most people were scared off.
I was terrified!

Why not create and output a simple XML form definition and transform it with XSLT? That way you could separate the layers even further while reducing the amount of inheritance you need.
Imho, that would only add another layer to the problem... Since in that case the OP will build an API for constructing XML... (How's that any different than what he's trying to do now? ;))

Posted: Sun Dec 24, 2006 5:31 am
by Kieran Huggins
It adds separation where separation is appropriate, imho. He's trying to use another layer of php class inheritance as a templating system when there's already an excellent templating system available in XSLT. Since he's building a forms library to separate his logic layer from his display layer, why not keep it entirely separate?

From another thread: ....something about jackhammering pudding to a wall... I hope the point was "use the right tool for the job". I think XSLT is that tool here.

Cheers,
Kieran

Posted: Sun Dec 24, 2006 8:18 am
by Ollie Saunders
Whoa, loads to reply to...
1) pass a renderer object to the Widget::render($renderer) instead
of the other way around,
I don't want my users to have to pass a renderer every time its much easier if a factory does this and besides its not possible when you are doing composite renders (a render from a form that also causes renders of all the widgets contained within it). Each widget has to store its own renderer as a private property.
so the widget becomes responsible
for rendering it, because as the widget gets more complicated
the rendering logic also rises in complexity.
Of course, there isn't any way to avoid such a thing though is there?
Also, how will a hierachy be rendered (at the moment, it seems to be
rendered explicitly by writing "<div>")?
Well that is one tiny prototype, I haven't exactly put in all the functionality! In the full version I do wrap each widget in a div that can be customized by "class", "css" and "js" classes. So you can make it look like this:

Code: Select all

<div class="these classes set" style="color:red" onclick="alert('foo')">
etc. I also use form, fieldset, ul and li for errors.
2) use a adapter for rendering if you need to render it differently,
I don't really understand how that would help? What exactly is wrong with a separate renderer is.
3) why does Widget have a label? does all widget have label? What if
I don't want a label?
Again, that code is a prototype, in the full version you just set it to null or false.
) why is Renderer::render() final?
My thinking was to avoid messing with the structure of the template function on which subclasses of Renderer would depend. In the full version I don't have a final there, it is much too rigid.
5) you may also abstract out a HTMLWriter rather than explicitly writing
HTML strings in the renders, this also allows you to use may be the
HTMLPurifier class...
Yes you are right. In the full version I have one and it is utterly essential.
6) rather than rendering the <option> tag inline, may be a class to render
the options?
I don't see how <option> rendering is a candidate for a separate class. Its not something that is likely to change and isn't big enough to need its own helper methods. Also its a job that only makes sense inside a select renderer.
another things may be needed are hierachices, for this use Composite
I don't use a composite in the prototype, the full version has one, several in fact its the cornerstone of the library and also one of the problem areas, unlike rendering.

-------------- Thanks for your comments wei, now on to kieran -----------------
Why not create and output a simple XML form definition and transform it with XSLT? That way you could separate the layers even further while reducing the amount of inheritance you need.
I could use XSLT as an alternate to rendering. But I fear I would have to construct XML for it to process first. It would probably make the renderer easier to write and I am looking to have it possible to write form definitions in XML but bottom line is you need to end up with PHP objects that you can do validation with....this is more than just transforming one thing into another; there's lots of conditionality I think XSLT would break down trying to do that.
Does that make any sense? I might be wayyy off the mark on this one!
Not at all. It definitely made me thing for a minute. But XSLT would introduce new problems and solve problems I don't really have. The rendering part of this library works really well and is nicely separated. Also so far I have no extension requirements for this library I don't particularly want people to require XSL to use it as most people don't have that installed.
Since in that case the OP will build an API for constructing XML
Ahh yes..great minds.


Guys, rendering isn't a problem here. I only called this painters and decorators because it was a good title :P. As you all seem to be interested in it, here is the renderer for text

Code: Select all

<?php
OF::loadClass('OF_Html_Renderer_Widget');
class OF_Html_Renderer_Widget_Text extends OF_Html_Renderer_Widget
{
    protected function _header()
    {
        $ent = $this->_entity;
        return $this->_tagPart('div', 1, $this->_getHeaderAttributes(false))
             . $this->_notifications()
             . $this->_tag(
                   'label',
                   $this->_addAccessKeyToLabel($ent->label, $ent->accessKey),
                   array('for' => $this->_entity->id)
               );
    }
    protected function _content()
    {
        $attr = $this->_entity->attributes;
        $ent = $this->_entity;
        if ($ent->password) {
            $attr->height = 1;      // Force <input> over <textarea>
            $ent->value = '';  // We don't repopulate password fields
            $type = 'password';
            $ent->autoComplete = null;
        } else {
            $type = 'text';
        }
        $commonTagAttr = array(
            'name'      => $ent->id,
            'disabled'  => $ent->disabled,
            'accesskey' => $ent->accessKey
        );
        if ($attr->height > 1) {
            // Don't replace array_merge with union (custom in $this->_getContentAttributes())
            if (!is_string($ent->value)) {
                $ent->value = '';
            }
            return $this->_tag(
                'textarea',
                $ent->value,
                array_merge(
                    $this->_getContentAttributes(true),
                    array(
                        'cols'     => $attr->width,
                        'rows'     => $attr->height,
                    ),
                    $commonTagAttr
                ),
                true
            );
        }
        // Don't replace array_merge with union (custom in $this->_getContentAttributes())
        $attributes = array_merge($this->_getContentAttributes(true), array(
            'type'         => $type,
            'autocomplete' => $ent->autoComplete,
            'size'         => $attr->width,
            'value'        => $ent->value
        ), $commonTagAttr);
        if ($ent->maxLength) {
            $attributes['maxlength'] = $ent->maxLength;
        }
        return $this->_tagPart('input', 0, $attributes);
    }
}

The problem I have is how to construct widgets using inheritance and decorators. I need the ability to separate components of functionality and combine them as they are needed into the object in some places (decorators). And I need the more concrete power of inheritance, in other places.

For instance I don't really want it to be possible to create abstract widget classes, you should only be able to create selects, buttons, texts etc. so perhaps that should be concrete inheritance...those things don't change either. Then I could use decorators for filtering, validations events, error/notification on top of these...but these decorators would need to work for all those different subclasses of widget and get access to lots of stuff in the object they are decorating.

Can anybody advise on how you can mix the two?

Posted: Sun Dec 24, 2006 8:18 pm
by Ollie Saunders
OK I've managed to do something quite promising: I have been able to do is create a Select that extends Widget and decorate it with SimpleNotes (this adds the ability to put messages above the widget itself). SimpleNotes works in such a way that it could decorate any kind of class at all so it can be added to all widgets.

In order to do this I've had to do 2 hacky things. I'm not sure how hacky they are so I'm looking for guidance as to whether they are unacceptably so.
  • First hacky thing is: I've used magic methods __get, __set and __call to perform the decoration in order to allow classes of any type to be decorated
  • Second hacky thing is: Because I need to get data from SimpleNotes back into Select (in order to send it to the renderer) the public render method on Widget now accepts a parameter $fromDecorators so that data can be sent back.
Now before I get on to the bad stuff there is two advantages to using the magic methods: I can use public properties again :) yay! and there is a lot less code just for chaining on to the decorated object...i.e. this:

Code: Select all

public function setLabel($newLabel) {
        return $this->_widget->{__FUNCTION__}($newLabel);
    }
    public function getRenderer() {
        return $this->_widget->{__FUNCTION__};
    }
ewww I hate that stuff.

About the first hacky thing: What worries me is that these magic method really do allow me to decorate anything. Is that likely to be a problem? I could put in checks and restriction, in fact I tried, but I realized it would be adding a fair overhead to each decoration. I was worried that because my new decorator wouldn't have the same type (as far as instanceof is concerned) as the type it is decorating that there would be problems but I managed to solve that with the getDecorated() method.

About the second hacky thing: uuuuhhhgg, who am I kidding. Nobody is reading this anyway. I might was well start calling all the mods names. feyd smells :wink:

Anyway here's the code:

Code: Select all

<?php
class Widget
{
    public $label;
    private $_id, $_renderer;
    public function __construct($id)
    {
        $this->setId($id);
    }
    public function getId()
    {
        return $this->_id;
    }
    public function setId($newId)
    {
        return $this->_id = $newId;
    }
    public function render(array $fromDecorators = array())
    {
        return $this->getRenderer()->render($this->_getDataForRenderer() + $fromDecorators);
    }
    public function getRenderer()
    {
        return $this->_renderer;
    }
    public function setRenderer(Renderer_Interface $newRenderer)
    {
        return $this->_renderer = $newRenderer;
    }
    protected function _getDataForRenderer()
    {
        return array('id' => $this->getId(), 'label' => $this->label);
    }
}
interface Renderer_Interface
{
    function render(array $transferData);
}
abstract class Renderer implements Renderer_Interface
{
    protected $_trans;
    // Implementation of template method pattern
    final public function render(array $transferData)
    {
        $this->_trans = (object)$transferData;
        return $this->_renderHead()
             . $this->_renderContent()
             . $this->_renderFoot();
    }
    protected function _renderHead()
    {
        $t = $this->_trans;
        $out = '<div class="field">';
        if (isset($t->SimpleNotes) && !empty($t->SimpleNotes['notes'])) {
            $out.= '<ul><li>' . implode('</li><li>', $t->SimpleNotes['notes']) . '</li></ul>';
        }
        if ($t->label) {
            $out.= '<label for="' . $t->id . '">' . $t->label . '</label>';
        }
        return $out;
    }
    abstract protected function _renderContent();
    protected function _renderFoot()
    {
        return '</div>';
    }
}
class Renderer_Select extends Renderer
{
    protected function _renderContent()
    {
        $t = $this->_trans; // $this->_trans already assigned by Renderer::render()
        $out = '<select id="' . $t->id . '" name="' . $t->id . '">';
        if (!empty($t->options)) {
            foreach ($t->options as $value => $label) {
                $out.= '<option value="' . $value . '">' . $label . '</option>';
            }
        }
        return $out . '</select>';
    }
}
abstract class SuperDecorator
{
    protected $_decorated;
    public function __construct($toDecorate)
    {
        $this->_decorated = $toDecorate;
    }
    public function __call($name, $params)
    {
        return call_user_func_array(array($this->_decorated, $name), $params);
    }
    public function __set($name, $value)
    {
    	$this->_decorated->{$name} = $value;
    }
    public function __get($name)
    {
    	return $this->_decorated->{$name};
    }
    final public function getDecorations($classNames = true)
    {
        if (method_exists($this->_decorated, __FUNCTION__)) {
            $decorations = $this->_decorated->{__FUNCTION__}($classNames);
        } else {
            $decorations = array();
        }
        $decorations[] = $classNames ? get_class($this) : $this;
        return $decorations;
    }
    final public function getDecorated()
    {
        if (method_exists($this->_decorated, __FUNCTION__)) {
            return $this->_decorated->{__FUNCTION__}();
        } else {
            return $this->_decorated;
        }
    }
}
class Select extends Widget
{
    private $_options;
    public function addOption($value, $label)
    {
        $this->_options[$value] = $label;
    }
    protected function _getDataForRenderer()
    {
        $data = parent::_getDataForRenderer();
        $data['options'] = $this->_options;
        return $data;
    }
}
abstract class WidgetDecorator extends SuperDecorator
{
    protected function _getDataForRenderer()
    {
        return array();
    }
    public function render(array $fromDecorators = array())
    {
        $data = $this->_getDataForRenderer();
        if (!empty($data)) {
            $data = array(get_class($this) => $data);
        }
        return $this->_decorated->{__FUNCTION__}($data + $fromDecorators);
    }
}
class SimpleNotes extends WidgetDecorator
{
    private $_notes = array();
    public function addNote($msg)
    {
        $this->_notes[] = $msg;
    }
    public function getNumNotes()
    {
        return count($this->_notes);
    }
    protected function _getDataForRenderer()
    {
        return array('notes' => $this->_notes);
    }
}
class Factory
{
    public function mkSelect($id, $label)
    {
        $select = new SimpleNotes(new Select($id));
        $select->label = $label;
        $select->setRenderer(new Renderer_Select());
        return $select;
    }
}
$factory = new Factory();
$select = $factory->mkSelect('pets', 'Choice of pets: ');
$select->addOption('cat', 'Cat');
$select->addOption('dog', 'Dog');
$select->addOption('rabbit', 'Rabbit');
$select->addNote('This is a select field');
$select->addNote('You are a monkey');
echo $select->render();
echo '<br />', print_r($select->getDecorations(true), true), '<br />', get_class($select->getDecorated());
and it produces this

Code: Select all

<div class="field"><ul><li>This is a select field</li><li>You are a monkey</li></ul><label for="pets">Choice of pets: </label><select id="pets" name="pets"><option value="cat">Cat</option><option value="dog">Dog</option><option value="rabbit">Rabbit</option></select></div><br />Array
(
    [0] => SimpleNotes
)
<br />Select
That's my last massive post for a while...I promise, a bit......slightly.

Posted: Mon Dec 25, 2006 4:05 pm
by wei
I don't want my users to have to pass a renderer every time its much easier if a factory does this and besides its not possible when you are doing composite renders (a render from a form that also causes renders of all the widgets contained within it). Each widget has to store its own renderer as a private property.
Of course, there isn't any way to avoid such a thing though is there?
passing a renderer does not prevent your widget doing composition (which should probably be implicit in the widget itself). So this is what I have suggested, for simplicity, let the Widget act as "Decorator" to it self and perform Composite rendering on it self. I think the reason you may see this as infeasiable is because at the moment, (may be just in the code you prototypes and posted here) you have rendered HTML code that is not related to the Select object at all. Each widget should only be responsible for rendering what is given and the minimum that it needs to be able to be displayed by the browser (may be this is already done, but not clear in the example code).

Generally, the "render()" method will be probably called only once or twice, so passing a renderer object is not such as problem. The renderer is mostly localised, that is, for the user the renderer does not need to be passed around. For the current setup of the renderer, you are really asking the user to write their own renderer, which defeats one of the advantages of the "form" widgets. I think these are good reasons not to use a separate renderer (as you have defined them), i would try a simple renderer that only provides a container for holding what to be rendered).

Wei.