I figured out why I'll never be happy w/ php forms libraries
Moderator: General Moderators
I figured out why I'll never be happy w/ php forms libraries
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.
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.
- 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
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...
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
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)
Re: I figured out why I'll never be happy w/ php forms libraries
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.
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
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)
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 ...
- 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
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: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)
Is it the verbosity that you think is the most important thing to deal with?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.
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)
Re: I figured out why I'll never be happy w/ php forms libraries
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)
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
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})Take a look here: http://www.djangoproject.com/documentat ... -templates
Re: I figured out why I'll never be happy w/ php forms libraries
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
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).
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();
}
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
- 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
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: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.
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)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)));
}
}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)
Re: I figured out why I'll never be happy w/ php forms libraries
Nope, I think thats as close as we're going to get.arborint wrote:Does that make sense? Do you think another implementation better expresses the Django code in PHP?
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'));
}
}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>Code: Select all
<form method="post" action="/nerds/add/">
{% for element in form %}
{{ element.as_dl }}
{% endfor %}
<input type="submit" value="Submit" />
</form>- 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
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:
looks up the validation requirements in the model, the named route for the post model's save action, nd outputs the following:
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.
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');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>- 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
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:Nope, I think thats as close as we're going to get.arborint wrote:Does that make sense? Do you think another implementation better expresses the Django code in PHP?
Let's compare some options: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:
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)));
}
}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'));
}
}Code: Select all
echo BlogPost->form('posts')
->input('subject')
->input('message')
->input('sender')
->input('cc_myself');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);
}
}(#10850)
- 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
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.
I think there's a much more verbose example in the skeleton discussions somewhere, along with some great feedback.
- 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
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.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:
Of course there are cases where I want a 100% completely custom form, but 99% of the time I need something like this.Code: Select all
<form method="post" action="/nerds/add/"> {% for element in form %} {{ element.as_dl }} {% endfor %} <input type="submit" value="Submit" /> </form>
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)
- 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
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.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.
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)
- 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
If I recall correctly, a populated form would be generated by passing a model $object instead of a Model to the generator:
v.s.
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.
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')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')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.
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.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.
- 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
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: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.
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: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.
I saw. Actually Matthijs and I have been doing the same project, so we may have an apples and apples comparison soon.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.
(#10850)