I figured out why I'll never be happy w/ php forms libraries

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

I figured out why I'll never be happy w/ php forms libraries

Post by Luke »

Not only is form development the longest, most tedious portion of development, it's also one of the most important to get right. Forms are the interface to your user. Because of this, they must be both secure, as well as easy to use. They are vitally important for the success of your application. This is why after almost three years of professional PHP development, I have still yet to find a method of forms development that I really like. The only one I like even a little bit so far is Django's newforms library, which is python, not PHP.

Why is it so hard to find a good solution for this in PHP? I think it has a lot to do with the fact that PHP is just too damn verbose. From the definition of the form to the template that renders it, PHP just requires too much work.

With every form, you have to deal with filtering, validation, escaping, rendering, error messages (and re-rendering), and probably several other things I'm not thinking of. Since 90% of the work that is required for all this is the same regardless of the actual content of the form, it seems that this process would be a great area for a library (to extract out the common functionality). The problem is that even when you do extract out all that code, many of the things that happen during the form handling process happen at completely different times (during different phases of the request) and the client code is still extremely verbose because every form's content is so different (even though the actual process is the same).

So what is the solution then? Create a forms language? Maybe an HTML-embedded language that renders your form and deals with all the difficult stuff for you? I don't really know. What do you guys think? Let's try and think outside the box here.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

I thought I had lost you to Zend_Form?!? ;) Been there and back... :) The question for me has always been: What are the specific things that are friction points for people with forms libraries?

The problem with automation is that there are too many different permutations to automate. Many years ago I gave up on trying to make simple forms easier and focused on making complex forms manageable. The result is that all forms, even simple ones, tend to be a similar amount of work for me (which is still too much ;)). I think that is not a trade-off that most people "looking" for a forms solution are willing to make. Once they try a few they may change their mind though.

A further question involves the MVC qualities of forms. Sometimes it makes sense to put filtering and validation in the Controller because it is Request data after all. Other times it makes sense to put the filtering and validation in the Model, because it is Domain data after all. How to support both or a mix? How to have sensible, secure defaults for newbies (Mordred help! ;))

Escaping is a tricky one too because one style is to use Helpers to render form inputs (it needs to escape), another style is to put them in the template (you need to escape). Do you need default On and turn Off as needed, or default Off and turn On as needed?

And how do you deal with a common rendering paradigm when some people use Helpers and others use Templates. And for select/radio/checkbox inputs that need to be set to selected/checked to a current/default state -- do you generate them with a Helper or use Javascript to set them? And how to you create a design where you can choose one or the other, and mix-and-match within the form?????

Then there are simple things like having the form show a text input for the key when creating a new record (insert), but echoing the value and/or putting a hidden input for the key when updating...

And on, and on, and on...
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Luke »

I have settled with Zend_Form for now simply because I don't know of a better solution. I like it about as much as I liked HTMLQuickForm... it works, so I use it. I'd much prefer a solution where I just defined the form and it worked. Django newforms comes the closest to fulfilling this wish with the smallest amount of client code.

Code: Select all

 
from django import newforms as forms
 
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)
 
Also, like you were saying, it would be nice if you could define a portion of your form in your model (since the code is mostly there anyway) but also be able to mix-and-match. Check out Django's solution to this problem: http://www.djangoproject.com/documentation/modelforms/

I just don't think there's a way of doing something like that in PHP. The only way I can see that working out in PHP is if maybe we used PHP to sort of read a text file with much less verbose syntax.

Code: Select all

 
form.method = post
    username = element.text(required=false, widget=elements.select, max_length=16)
    password = element.text(widget=elements.password)
    ... etc ... 
 
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

The Ninja Space Goat wrote:

Code: Select all

 
from django import newforms as forms
 
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)
 
You are going to need to help us out a little with that example. What does that actually define, how to you show the form and what will it look like?
The Ninja Space Goat wrote:I just don't think there's a way of doing something like that in PHP. The only way I can see that working out in PHP is if maybe we used PHP to sort of read a text file with much less verbose syntax.
Is it the verbosity that you think is the most important thing to deal with?

I propose that we start with the simplest use case example you can think of -- maybe one of the above -- and see how we could express that in PHP. Then we can figure out what code is need under the hood to run that code.
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Luke »

Well right now, I'm only talking about form definition. The actual definition of what elements the form consists of and how it is to be validated, filtered, etc. I was of the opinion that filtering / validation should be completely separate from form definition, but honestly it just causes too much work in practice. So, I am satisfied with a sort of validation framework that is separate from, but used by, the form library. What I mean by that is that although there would be methods to easily validate / filter your form elements, the actual filtering / validation code would be separate from the forms library.

In the python example, you would see code like this in the controller (or as they call it, the view)

Code: Select all

from django.newforms import newforms as form
from myapp.forms import ContactForm
 
def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # Do form processing here...
            return HttpResponseRedirect('/url/on_success/')
    else:
        form = ContactForm()
    return render_to_response('contact.html', {'form': form})
In your view, you have several options. You may just render the form and be happy with their default rendering, or if you dont like it, you can render each element individually, or you can use one of several options they provide (as_ul, as_p, as_table)

Take a look here: http://www.djangoproject.com/documentat ... -templates
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: I figured out why I'll never be happy w/ php forms libraries

Post by VladSun »

I'm almost happy with CodeIgniter - in fact there is no complex FORM Generator library, but simple implementation (some say not good) of the MVC concept. And I really like the filter/validation class in CI, although I have written some more rules which I missed.

Real code example:

controller

Code: Select all

 
    function edit($id)
    {
        $id = intval($id);
        $this->params['id'] = $id;
        
        $this->menu = 'users';
        $this->main = 'users/edit';
        
        $rules['fullname']      = "xss_clean|trim|required|min_length[4]|max_length[80]|htmlspecialchars";
        $rules['email']         = "xss_clean|trim|required|valid_email";
        $rules['phone']         = "xss_clean|trim|required|alpha_dash";
        $rules['is_admin']      = "integer";
        $rules['safe']          = "integer";
 
        $fields['fullname']     = '<span class="error">full name</span>';
        $fields['email']        = '<span class="error">email</span>';
        $fields['phone']        = '<span class="error">phone</span>';
        $fields['is_admin']     = '<span class="error">is adminitstrator</span>';
        $fields['safe']         = '<span class="error">is trusted</span>';
        $fields['submitted']    = '';
 
        $this->validation->set_rules($rules);
        $this->validation->set_fields($fields);
 
        $user = $this->users->load($id);
        $this->validation->username = $user->username;
 
        $this->params['values'] =& $this->validation;
        $this->params['error']  = '';
 
        if (empty($this->validation->submitted))
            $this->params['values'] = $user;
        elseif ($this->validation->run() === false)
            $this->params['error'] = $this->validation->error_string;
        elseif ($_SESSION['user_id'] == $id && empty($this->validation->is_admin))
            $this->params['error'] = $this->lang->line('admin_user_edit cant_revoke_own_privileges');
        elseif ($this->users->email_exist($this->validation->email, $id))
            $this->params['error'] = $this->lang->line('admin_user_edit email_already_exist');
        elseif ($this->users->update($id, $this->validation) === false)
            $this->params['error'] = $this->lang->line('admin_user_edit unable_to_save');
                else
            redirect('/users/roll', 'location');
 
        $this->_view();
    }
 
I can't see how one could generalize this ... There would be always specific rules and limitations you have to handle.

view
Pure HTML with echoing $values->field. I dislike the idea of using (another?) template language in PHP.

model
You can imagine what's in the model ;)

One could write a form-view generator using the data in $fields, but I think It's a little bit useless - again almost every time you'll need some customization.

The worst headache I've had with forms was re-rendering AJAX form elements (like chained selects).
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

The Ninja Space Goat wrote:Well right now, I'm only talking about form definition. The actual definition of what elements the form consists of and how it is to be validated, filtered, etc. I was of the opinion that filtering / validation should be completely separate from form definition, but honestly it just causes too much work in practice. So, I am satisfied with a sort of validation framework that is separate from, but used by, the form library. What I mean by that is that although there would be methods to easily validate / filter your form elements, the actual filtering / validation code would be separate from the forms library.
OK ... let's just focus on the form definition. I agree with you that not having the filtering and validation in the Controller in the controller "just causes too much work in practice." Let's look at the Django code:

Code: Select all

class ContactForm(forms.Form):
   subject = forms.CharField(max_length=100)
   message = forms.CharField()
   sender = forms.EmailField()
   cc_myself = forms.BooleanField(required=False)
I am guessing that in PHP it might look something like this:

Code: Select all

class ContactForm extends Form {
   public function __construct() {
      $this->forms('subject', new CharField(array('max_length'=>100)));
      $this->forms('message', new CharField());
      $this->forms('sender', new EmailField());
      $this->forms('cc_myself', new BooleanField(array('required'=>False)));
   }
}
Does that make sense? Do you think another implementation better expresses the Django code in PHP?

One thing I immediately noticed about this code is that it is mostly about generating HTML form inputs. There is an implied required=true for the other fields, but that is about it. Why I noticed is that I use templates for forms so don't think about generating HTML so much.
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Luke »

arborint wrote:Does that make sense? Do you think another implementation better expresses the Django code in PHP?
Nope, I think thats as close as we're going to get.

Actually, I handled my forms the same way for a long time. I generally have relied on a filtering / validation library as well as templates to render my forms. Then I just tied it all together with the names, like so:

Code: Select all

class NerdsController
{
    public function addAction() {
 
        if ($this->getRequest()->isPost()) return; // renders template automagically
        $post = $this->getRequest()->getPost();
        $input = new MC2_Input($post);
        $input->setRequired('username', 'email', 'someotherfield');
        $input->addValidor(new MC2_Validator_Whatever(''), array('username', 'email'));
 
    }
}
template:

Code: Select all

<form method="post" action="/nerds/add">
    <dl>
        <dt <?php if ($this->formHasError('username')) echo 'class="error"'; ?>>
            <label>Username:</label>
            <?php echo $this->formError('username'); ?>
        </dt>
        <dd><?php echo $this->formText('username'); ?></dd>
        
        <dt <?php if ($this->formHasError('email')) echo 'class="error"'; ?>>
            <label>E-mail address:</label>
            <?php echo $this->formError('email'); ?>
        </dt>
        <dd><?php echo $this->formText('email'); ?></dd>
        
        <dt <?php if ($this->formHasError('someotherfield')) echo 'class="error"'; ?>>
            <label>Some Other Field:</label>
            <?php echo $this->formError('someotherfield'); ?>
        </dt>
        <dd><?php echo $this->formSelect('someotherfield', $options); ?></dd>
    </dl>
</form>
The problem with this is that it doesn't really save you much work. You still have to write out the template code. This sucks especially in this case where the form follows a VERY repetitive format. I would much rather (at least in this case) do something like this django example:

Code: Select all

<form method="post" action="/nerds/add/">
    {% for element in form %}
        {{ element.as_dl }}
    {% endfor %}
    <input type="submit" value="Submit" />
</form>
Of course there are cases where I want a 100% completely custom form, but 99% of the time I need something like this.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Kieran Huggins »

while being DRY is important, I would never want form xhtml auto-generated entirely. The order things are displayed is important for a good UI.

That being said, there are certainly great strides to be made in the area of validation and field generation. I'd love to be able to define a model and a router and use the model to generate fields:

Code: Select all

echo BlogPost->form('posts','create')->input('title')->input('body');
looks up the validation requirements in the model, the named route for the post model's save action, nd outputs the following:

Code: Select all

<form method="post" action="/posts/new">
  <input type="text" name="blogPost['title']" val:regex="\w{1,255}" />
  <textarea name="blogPost['body']" val:regex="\w{1,65535}"></textarea>
  <input type="submit" />
</form>
With the val:regex attributes you can optionally use a client-side library to verify your form inputs. Nice and DRY, and completely MVC. I do love my TLA's.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

The Ninja Space Goat wrote:
arborint wrote:Does that make sense? Do you think another implementation better expresses the Django code in PHP?
Nope, I think thats as close as we're going to get.
Ok and Huh? I guess from your comment that you think that is as close as we can get in PHP, but you don't like it. ;)
The Ninja Space Goat wrote:Actually, I handled my forms the same way for a long time. I generally have relied on a filtering / validation library as well as templates to render my forms. Then I just tied it all together with the names, like so:
Let's compare some options:

Django style mixes filtering/validation and HTML generation. Individual assignment of field objects of different types. Each object contains rules. Not sure about filters.

Code: Select all

class ContactForm extends Form {
   public function __construct() {
        $this->forms('subject', new CharField(array('max_length'=>100)));
        $this->forms('message', new CharField());
        $this->forms('sender', new EmailField());
        $this->forms('cc_myself', new BooleanField(array('required'=>False)));
   }
}
Ninja's is all filtering and validation and no HTML generation. Standard object usage. Group assignment of filters and rules.

Code: Select all

class NerdsController
{
    public function addAction() {
 
        if ($this->getRequest()->isPost()) return; // renders template automagically
        $post = $this->getRequest()->getPost();
        $input = new MC2_Input($post);
        $input->setRequired('subject', 'message', 'sender');
        $input->addValidor(new MC2_Validator_MaxLength(100), array('subject'));
 
    }
}
Kieran is all HTML generation and no filtering and validation. Fluent interface. No filters or validators present. They would be done separately, preferably in the Model, as I recall from conversations with Kieran. And, javascript is used more.

Code: Select all

echo BlogPost->form('posts')
                        ->input('subject')
                        ->input('message')
                        ->input('sender')
                        ->input('cc_myself');
You can look at Zend_Form here. They are closer to Django forms. One nice thing they do is allow the text name of the helper or an instance for filters and rules. And they allow you to configure with an array.

I'll throw Skeleton's low level style (still need high level) because it is more verbose, but completely customizable. Individual parameter objects. Each object contains any mix of rules and filters. each can have a Helper to render the value.

Code: Select all

class ContactForm extends A_Controller_Form {
   public function __construct() {
        $subject = new A_Controller_FormParameter('subject');
        $subject->addRule(new A_Rule_Notnull('subject', 'subject error message'));
        $this->addParameter($subject);
 
        $message = new A_Controller_FormParameter('message');
        $message->addRule(new A_Rule_Notnull('message', 'message error message'));
        $this->addParameter($message);
 
        $sender = new A_Controller_FormParameter('sender');
        $sender->addFilter(new A_Filter_Regex[('/[^a-zA-Z\@\.\-\_]/'));
        $sender->addFilter(new A_Filter_Tolower[());
        $sender->addRule(new A_Rule_Email('sender', 'sender error message'));
        $this->addParameter($sender);
 
        $cc_myself = new A_Controller_FormParameter('cc_myself');
        $cc_myself->addFilter(new A_Rule_Regex[('/[^01]/'));
        $cc_myself->setType(array(
                                'renderer'=>'A_Html_Form_Select',
                                'values'=>array(1,0),
                                'labels'=>array('Yes','No')));
        $this->addParameter($cc_myself);
   }
}
This is just the configuration code. The thing about the above styles, is that they are all useful in different situations. It depends on the complexity of the code and whether you are using templates or not.
(#10850)
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Kieran Huggins »

It should be noted that my take on it is reliant on a system with an expected interface. Either the model itself is responsible for form generation (or better yet) or has an interface that the form generator can query for validation information.

I think there's a much more verbose example in the skeleton discussions somewhere, along with some great feedback.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

The Ninja Space Goat wrote:The problem with this is that it doesn't really save you much work. You still have to write out the template code. This sucks especially in this case where the form follows a VERY repetitive format. I would much rather (at least in this case) do something like this django example:

Code: Select all

<form method="post" action="/nerds/add/">
    {% for element in form %}
        {{ element.as_dl }}
    {% endfor %}
    <input type="submit" value="Submit" />
</form>
Of course there are cases where I want a 100% completely custom form, but 99% of the time I need something like this.
A completely separate issue is how the form is rendered. Django has all those as_dl, as_table, etc. methods for different kinds of output. It does get annoying to have to generate all those identical blocks for each input. So you want to be able to generate the form fields and some surrounding HTML code. That is mainly for for CRUD forms. For custom front-end forms I find that there is a custom layout so it needs to be a template. Some of that could be done with styles, but the city/state/zip on one line is a common example of needing custom layout.

Kieran does more with CSS/Javascript. I am assuming that he is even setting the values somehow. So being able to generate Javascript data or actual Javascript is another direction you can go with a forms solution:

Code: Select all

<form method="post" action="/posts/new">
  <input type="text" name="blogPost['title']" val:regex="\w{1,255}" />
  <textarea name="blogPost['body']" val:regex="\w{1,65535}"></textarea>
  <input type="submit" />
</form>
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

Kieran Huggins wrote:It should be noted that my take on it is reliant on a system with an expected interface. Either the model itself is responsible for form generation (or better yet) or has an interface that the form generator can query for validation information.

I think there's a much more verbose example in the skeleton discussions somewhere, along with some great feedback.
Yes, the whole issue of where the data comes from to initialize the form and where it goes with the form is valid are another whole discussion.

A problem I have is that we are rarely comparing actual working code. It would be good to actually build implementations in several styles, plus the identical form in Zend, to really compare the styles.
(#10850)
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Kieran Huggins »

If I recall correctly, a populated form would be generated by passing a model $object instead of a Model to the generator:

Code: Select all

// populate the post object
$post = Post('post_id');
// render the (populated) form
echo Form->for($post)
  ->method('post')
  ->action('/posts/update') // this could be had from a compatible router
  ->input('title')
  ->input('body')
v.s.

Code: Select all

// render the form
echo Form->for(Post)
  ->method('post')
  ->action('/posts') // this could be had from a compatible router
  ->input('title')
  ->input('body')
Also, I'm assuming that a _toString magic method exists to render the Form object in each case. I like using _toString since I can generate a form object into a $var and just echo it wherever. Makes for prettier templates.

I also remember now that the default behaviour was to generate labels, which is better IMO.

As for the javascript, I would use jQuery (go figure!) to do client side validation based on the val: namespaced attributes, but it's obviously not required since the model should validate the data anyway.
arborint wrote:A problem I have is that we are rarely comparing actual working code. It would be good to actually build implementations in several styles, plus the identical form in Zend, to really compare the styles.
I'd actually love to build it, I just don't have time right now... still working on a big project. I really do want to, though.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: I figured out why I'll never be happy w/ php forms libraries

Post by Christopher »

Kieran Huggins wrote:If I recall correctly, a populated form would be generated by passing a model $object instead of a Model to the generator:

Also, I'm assuming that a _toString magic method exists to render the Form object in each case. I like using _toString since I can generate a form object into a $var and just echo it wherever. Makes for prettier templates.

I also remember now that the default behaviour was to generate labels, which is better IMO.
I believe that I ended up actually implementing (almost) everything that you requested. There is a setModel() method that provides the data to the form fields. It can be a Model or the Request as I recall.
Kieran Huggins wrote:As for the javascript, I would use jQuery (go figure!) to do client side validation based on the val: namespaced attributes, but it's obviously not required since the model should validate the data anyway.
Yes. The thing I am interested in is what data or Javascript the form code would need to generate to make the client and server code work together. And we have not even broached Ajax forms or fields.
Kieran Huggins wrote:I'd actually love to build it, I just don't have time right now... still working on a big project. I really do want to, though.
I saw. Actually Matthijs and I have been doing the same project, so we may have an apples and apples comparison soon. ;)
(#10850)
Post Reply