How to design an HTML Form class

I'm trying to design an HTML Form class and I need some help with some concepts.

Buttons, for example, are easy to visualize as "objects" but when I create an HTML form, all I really need is for the code (class, function, method, or whatever I wind up using) to write a bunch of HTML. The button has no properties and the only method is writing itself as HTML code.

Does this mean that it makes more sense to write the button code as a function as opposed to a class? Or should the button instead of being an object be a method of the Form class? The same applies to all the other form objects such as select, radio buttons and check boxes.

BTW, I'm saving the state of radio buttons and other elements in a different object such as a shopping cart.

Thanks for your help!

I would call a button a form element, much like input, label etc...

As such, I would have a Form object which accepted Form_Element objects to compose the form.

Which makes a button an object which is where I started but then I noticed that this "object" would only have one method and no properties which is what led me to ask the question if it was worth creating a Button class, a Select class, etc.

SilverBullet, your answer is yes, go for it! Right?

A button has plenty of attributes -- I'm assuming these are your properties? ID, name and class for starters. I'm not sure how you've structured your classes but here's what I would do.

class Form_Element_Abstract
{
    protected $_attributes;
    public function addAttribute($name, $value){}
    public function setAttribute($name, $value){}
    public function getAttribute($name){}
    public function isAttribute($name){}
    protected function _buildAttributes(){}
    abstract public function render();
}

class Form_Element_Button extends Form_Element_Abstract
{
    public function render()
    {
        $openTag = '<button';
        $closeTag = '</button>';
        $xhtml = $openTag
               . $this->_buildAttributres()
               . '/>'
               . $closeTag;

        return $xhtml;
    }
}

If it's needed you can add wrapper functions around setAttribute to create a function to set ID and name, but it's extensible so can set any attribute that a button has.

Thanks for the code sample. I've not structured anything yet, I write mostly procedures and I'm still trying to get my head around OOP.

Looking at your code I think that the issue to solve is nesting a group of tags inside other tags (radio buttons inside a field set, options inside a select), and all these tags inside the form tag in addition to populating the tags with name=value pairs (attributes). The class could be a lot more general than just a form builder.

An article I read recently suggested that the most efficient way to pass parameters to an object is to pass a single associative array and that already has the name=value pairs.

Just thinking out loud. smile

Most of the answers you'll get in the Application Design forum will be OOP-based because that's what Application Design is about.

Not really efficient in terms of the program - Efficient for the programmer, maybe - but not by much.

I'd be careful with such a broad statement.

That's not always true.

That's good because I was looking for a place to discuss OOP. I recently upgraded myself to php 5 and I'm anxious to leant to use the object model. I've been playing around with OOP for a long time but I never used it for serious work until recently because I have a hard time conceptualizing programming objects as you can see from my trashing around with this form thing. wink

The only production object code I have written is a File class that manages various types of files: mail attachments, uploads, downloads, file storage in MySQL.

...because that's what Application Design is about.

For me, Application Design is 40% user interface, 40% data structure. You can use a multitude of tools to get there.

I was thinking something more along these lines...

<?php
class Form
{
    protected $sAction;
    
    protected $sMethod;
    
    protected $aElements = array();
    
    public function __construct($sAction, $sMethod = 'post')
    {
        $this->sAction = $sAction;
        $this->sMethod = $sMethod;
    }
    
    public function addElement(FormElement $oElement)
    {
        $this->aElements[] = $oElement;
    }
    
    public function render()
    {
        foreach ($this->aElements as $oElement)
        {
            $sElements .= $oElement->render() . "\\r\
";
        }
        return sprintf(
            '<form action="&#37;s" method="%s">
                %s
            </form>',
            $this->sAction,
            $this->sMethod,
            $sElements
        );
    }
}

abstract class FormElement
{
    protected $aAttributes;
    
    public function addAttribute($sName, $mValue)
    {
        $this->aAttributes[$sName] = $mValue;
    }
    
    abstract public function render();
}

class TextFormElement extends FormElement
{
    public function render()
    {
        foreach ($this->aAttributes as $sName => $mValue)
        {
            $sAttributes .= sprintf('%s="%s" ', $sName, $mValue);
        }
        return sprintf(
            '<input type="text" %s/>',
            $sAttributes
        );
    }
}

class PasswordFormElement extends FormElement
{
    public function render()
    {
        foreach ($this->aAttributes as $sName => $mValue)
        {
            $sAttributes .= sprintf('%s="%s" ', $sName, $mValue);
        }
        return sprintf(
            '<input type="password" %s/>',
            $sAttributes
        );
    }
}

class SubmitFormElement extends FormElement
{
    public function render()
    {
        foreach ($this->aAttributes as $sName => $mValue)
        {
            $sAttributes .= sprintf('%s="%s" ', $sName, $mValue);
        }
        return sprintf(
            '<input type="submit" %s/>',
            $sAttributes
        );
    }
}

$oForm = new Form('/users/login/');

$oUsernameField = new TextFormElement();
$oUsernameField->addAttribute('name', 'username');

$oPasswordField = new PasswordFormElement();
$oPasswordField->addAttribute('name', 'password');

$oSubmitButton = new SubmitFormElement();
$oSubmitButton->addAttribute('name', 'submit');
$oSubmitButton->addAttribute('value', 'login');

$oForm->addElement($oUsernameField);
$oForm->addElement($oPasswordField);
$oForm->addElement($oSubmitButton);
echo $oForm->render();
/*
<form action="/users/login/" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" name="submit" value="login" />
</form>
*/
?>

Of course there are lots of ways this can be improved, but it should give you a rough idea.

It depends. Making the form element an object, rather than a method, you gain the advantage of polymorphism - Which is really just a type of decoupling. Decouplings are good if they are used in the proper places, but they add unnecessary complexity, if they are placed in the wrong places. In this case, it's pretty common to make the form elements objects, but you might want to start out with a less flexible - simpler - design, and change it when you see the need.

Thank you so much for that code, it sure helps!

I don't see the need to have a class for each form element so I simplified your code a bit. My version has a single Tag class and the constructor is passed the actual tag (input, button, etc.):

<?php
class Form
{
    protected $sAction;

    protected $sMethod;

    protected $aTags = array();

    public function __construct($sAction, $sMethod = 'post')
    {
        $this->sAction = $sAction;
        $this->sMethod = $sMethod;
    }

    public function addTag(Tag $oTag)
    {
        $this->aTags[] = $oTag;
    }

    public function render()
    {
        foreach ($this->aTags as $oTag)
        {
            $sTags .= $oTag->render() . "\\r\
";
        }
        return sprintf(
            '<form action="%s" method="%s">
                %s
            </form>',
            $this->sAction,
            $this->sMethod,
            $sTags
        );
    }
}

class Tag
{
    protected $sTag;

    protected $sDisplayText;

    protected $aAttributes;

    public function __construct($sTag, $sDisplayText = '')
    {
        $this->sTag = $sTag;
        $this->sDisplayText = $sDisplayText;
    }

    public function addAttribute($sName, $mValue)
    {
        $this->aAttributes[$sName] = $mValue;
    }

    public function render()
    {
        if (is_array($this->aAttributes))
        {
            foreach ($this->aAttributes as $sName => $mValue)
            {
                $sAttributes .= sprintf('%s="%s" ', $sName, $mValue);
            }
        }
        if ($this->sDisplayText == '')
        {
            return sprintf(
                '<%s %s/>',
                $this->sTag,
                $sAttributes
            );
        } else {
            return sprintf(
                '<%s %s>%s</%s>',
                $this->sTag,
                $sAttributes,
                $this->sDisplayText,
                $this->sTag
            );
        }
    }
}


$oForm = new Form('/users/login/');

$oUsernameField = new Tag('input');
$oUsernameField->addAttribute('type', 'text');
$oUsernameField->addAttribute('name', 'username');

$oBr = new Tag('br');

$oPasswordField = new Tag('input');
$oPasswordField->addAttribute('type', 'password');
$oPasswordField->addAttribute('name', 'password');

$oSubmitButton = new Tag('button', 'Click Me!');
$oSubmitButton->addAttribute('type', 'submit');
$oSubmitButton->addAttribute('name', 'submit');
$oSubmitButton->addAttribute('value', 'login');

$oForm->addTag($oUsernameField);
$oForm->addTag($oBr);
$oForm->addTag($oPasswordField);
$oForm->addTag($oBr);
$oForm->addTag($oSubmitButton);

echo $oForm->render();
/*
<form action="/users/login/" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit" name="submit" value="login" >Click Me!</button>
</form>
*/
?>

Would you be kind enough to explain this line of code

public function addTag(Tag $oTag)

I don't understand the function of the class name parameter it takes.

It is type hint:
http://www.php.net/manual/en/language.oop5.typehinting.php

Thanks! wink

A class for each element because each element separate object. You don't want to over simplify the relation between objects. It will come back to bite you eventually.

With your help I've built a Tag class that can write a whole HTML page.

<?php

class Tag
{
    protected $sTag;
    protected $sAttributes;
    protected $sContent;

    public function __construct($sTag, $sContent = '')
    {
        $this->sTag = $sTag;
        $this->sContent = $sContent;
    }

    public function addAttribute($sName, $mValue)
    {
        $this->sAttributes .= "{$sName}='{$mValue}' ";
    }

    public function addTag(Tag $oTag)
    {
        if ($this->sContent == '')
        {
            $this->sContent = "\
";
        }
        $this->sContent .= $oTag->render() . "\
";
    }

    public function render()
    {
        $temp = trim("{$this->sTag} {$this->sAttributes}");
        if ($this->sContent == '')
        {
            return "<{$temp} />";
        }
        return "<{$temp}>{$this->sContent}</{$this->sTag}>";
    }
}

/************* test *************/

$oTitle = new Tag('title', 'Denny\\'s Object Stuff');

$oKeywords = new Tag('meta');
$oKeywords->addAttribute('name', 'keywords');
$oKeywords->addAttribute('content', 'OOP, PHP5');

$oHead = new Tag('head');
$oHead->addTag($oTitle);
$oHead->addTag($oKeywords);

$oBr = new Tag('br');

$oUsernameField = new Tag('input');
$oUsernameField->addAttribute('type', 'text');
$oUsernameField->addAttribute('name', 'username');

$oPasswordField = new Tag('input');
$oPasswordField->addAttribute('type', 'password');
$oPasswordField->addAttribute('name', 'password');

$oSubmitButton = new Tag('button', 'Click Me!');
$oSubmitButton->addAttribute('type', 'submit');
$oSubmitButton->addAttribute('name', 'submit');
$oSubmitButton->addAttribute('value', 'login');

$oFieldset = new Tag('fieldset');
$oFieldset->addAttribute('style', 'width:200px;');
$oLegend = new Tag('legend', 'Log in please');
$oFieldset->addTag($oLegend);
$oFieldset->addTag($oUsernameField);
$oFieldset->addTag($oBr);
$oFieldset->addTag($oPasswordField);
$oFieldset->addTag($oBr);
$oFieldset->addTag($oSubmitButton);

$oForm = new Tag('form');
$oForm->addAttribute('action', $_SERVER['PHP_SELF']);
$oForm->addAttribute('method', 'post');
$oForm->addTag($oFieldset);

$oBody = new Tag('body');
$oBody->addTag($oForm);

$oHtml = new Tag('html');
$oHtml->addAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
$oHtml->addAttribute('xml:lang', 'en');
$oHtml->addAttribute('lang', 'en');
$oHtml->addTag($oHead);
$oHtml->addTag($oBody);

echo $oHtml->render();

/*
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<title>Denny's Object Stuff</title>
<meta name='keywords' content='OOP, PHP5' />
</head>
<body>
<form action='/~denny/php_manual/xhtml.php' method='post'>
<fieldset style='width:200px;'>
<legend>Log in please</legend>
<input type='text' name='username' />
<br />
<input type='password' name='password' />
<br />
<button type='submit' name='submit' value='login'>Click Me!</button>
</fieldset>
</form>
</body>
</html>
*/
?>

Now I need to create new classes to build selectors and sets of radio buttons and checkboxes.

Thanks guys!

I would apply a factory pattern and daisy chain.

// THis
$oUsernameField = new Tag('input');
$oUsernameField->addAttribute('type', 'text');
$oUsernameField->addAttribute('name', 'username');

// Become this
Tag::get('input')->addAttribute('type', 'text')->addAttribute('name', 'username');

// add $attribs so you can add your attributes from your constructor
public function __constructor($sTag, array $attribs = null)
{}

// Factory pattern.
public function get($sTag, array $attribs = null)
{
    return new Tag($sTag, $attribs);
}

public function addAttribute($name, $value)
{
  // Keep the attributes as an array until you need it rendered
  $this->_attribs[$name] = $value;
  returh $this; // daisy chained
}

One more thing. Your tag class shouldn't be concerned with the over content. It's purpose is basically to handle tags, i.e. to alter and render a tag with it's attributes. If you want to render an entire set of tags then that's a different class all together. In any case I thought this was about forms? O.o

Most tags have the same structure, a tag name, zero or more attributes and optional content. I don't see why these would need separate objects. But there are specialized tags, I can think of three: select, radio, and checkbox, that do need their own classes which I plan on building.

I don't see the need to have a class for each form element

If you wanted to target a specific element as you add it to the form, a decorator say, you would not be able to do this with your proposed solution.

Without rendering the tag, there is no way to determine which form element it represents.

Anthony, sorry, but I don't understand what you are saying. Each element or tag has its name and until I render it, I can add stuff to it.

But it does make sense to have a class that assembles the tags to build the page (or the form)

Without rendering the tag, there is no way to determine which form element it represents.

Again, I don't understand. confused

Maybe this clears it up a little. smiley

class LimitedTextFieldsForm extends Form
{
    public function addElement(FormElement $oElement)
    {
        if($oElement instanceof TextFormElement)
        {
            $oElement->addAttribute('max-length', 5);
        }
        $this->aElements[] = $oElement;
    }
}

Or more likely

class DisabledSubmitFieldForm extends Form
{
    public function addElement(FormElement $oElement)
    {
        if($oElement instanceof SubmitFormElement)
        {
            $oElement->addAttribute('disabled', 'disabled');
        }
        $this->aElements[] = $oElement;
    }
}

How would you do this given your solution?