Abstract Form Handling
I've already gone over some basic form handling and form best practices with my last few posts, but building with an object-orientated MVC starts to throw forms into a different light. It's very easy to abstract out forms with their repetitive logic patterns, something that I've recently done on one of my side projects. While I don't want to explain the code line-by-line, this post will go over some of the basic thought processes and steps I took to make my abstracted form handler.
Form Wrapper
The first logic I worked out was the definition of a form. To create each new form, I create a new class that is abstracting off a base pattern. Each form class defines form elements within a standard 'get' method. This way I can call on individual form elements easily or call the entire class to pull the whole form. Example...
private $username_input;
public function getUsernameInput()
{
if(!$this->username_input)
{
$this->username_input = new TextInput(
self::USERNAME,
Validate::USERNAME,
true,
true);
}
return $this->username_input;
}
Note: This setup is experimental... I've already been thinking of ways to lay out this logic in a cleaner and less verbose way.
Form Elements
The easiest thing to look for repeating logic patterns is how PHP and HTML handle different form elements. A text input should have a label and name attribute, while a radio input needs several different labels but the same name shared across the different options. I broke this logic down to different classes - one class per element type, all abstracting out a base class. So, in the code snippet above, I define a new TextInput with a name attribute of 'self::USERNAME' and validation attribute of 'Validate::USERNAME'.
Validation
There is actually two levels of validation. Once a form realizes that it's been submitted, it pulls all of the get methods via a Reflection Class operation and runs the defined validation on a per-element basis. After it runs the defined validation, it looks up to see if there are any additional requirements within the form class. If there is, it runs those as well. For the snippet above, the first check is to see if the username fulfills Validate::USERNAME requirements, and a second one (defined as 'validateUsernameInput' in the form class) checks for username uniqueness.
Displaying the Form
I did not want to have to type an actual form element ever again with this new system, so each form element has a custom display defined within the class. Since I'm still in the testing phase of this system I broke down the display into different pieces... getLabelElement, getErrorMessage, and getInputElement. Within my view I call these pieces in order that I want the element to be displayed, and each form element is ordered by the view.
These are a few of the basic points of the abstracted out form system. You may be wondering why I'd go through the trouble of all this... wouldn't it be easier to just type everything out manually? The immediate benefit is a based on the idea of abstraction itself - this form can be deployed to any of my pages with a few lines of code. Since the view and validation is wrapped up independent of the typical routing/controller logic, the independent controllers remain focused on the view and are not cluttered with lines of form validation.
Other benefits will start showing up once I start using this logic in more places and get used to the real-life application. It'd be easy to add ajax validation to all of the forms using the same validating methods that the backend calls upon. I also see this logic being utilized in abnormal traffic (like API calls). I've already noticed an immediate benefit - it is much easier to write a single class defining each element than rewriting a bunch of form handling checks scattered throughout my code base!
Comments (0)