Lesson 10 - FormControl - Parent of form controls in PHP
In the previous lesson, Building form framework for PHP - HtmlBuilder, we created an HTML builder which makes generating short HTML fragments easy. In today's tutorial, we'll create an abstract FormControl class which will serve as the parent of all concrete form controls (e.g. for the TextBox).
FormControl
The FormControl class will be the parent for all form controls. It'll contain a validating mechanism and several common properties and methods to ensure that all the form controls have the same interface. Let's go ahead and add an empty class:
abstract class FormControl { }
Common properties
We'll start off with the easy stuff. Every control will a name, a label and HTML attributes. We'll add these properties into the class and initialize them in the constructor:
public $label; public $htmlParams = array(); public $name; public function __construct($name, $label = '', $htmlParams = array()) { $this->name = $name; $this->label = $label; $this->htmlParams = $htmlParams; $this->htmlParams['name'] = $name; $this->htmlParams['id'] = $name; }
We set the control's name and ID right into its HTML parameters. These are HTML attributes which will be added to the element while it's being rendered (converted to HTML code).
While on the topic of simple properties, let's go ahead and add a setter for the ToolTip property. This property is a text which is displayed when you hover the mouse cursor over a control. We set it to the title HTML attribute. Typically, it contains hints at what you should type into the field:
public function setTooltip($toolTip) { $this->htmlParams['title'] = $toolTip; return $this; }
In PHP, we usually don't use getters and setters as much as in languages like Java. However, in this case, they play an important role. Notice how the setter returns a FormControl instance (all setters will do this). In order to set properties without setters, we'd have to write something like this:
$jmenoBox = $form->addTextBox('name', 'Name', true); $jmenoBox->toolTip = 'Enter your full name'; $jmenoBox->text = 'John Smith';
Thanks to the fact that setters return an instance on which we can call another method, we are able to chain methods easily. Meaning that the code above can be shortened to this:
$form->addTextBox('name', 'Name', true) ->setToolTip('Enter your full name') ->setText('John Smith');
Hopefully, you agree that this variant is way more readable. Form definitions will be rather long, so briefness matters.
Let's add one more method to the control which will be used to assign a CSS class. Keep in mind that the instance could already have a class set to it:
public function addClass($class) { if (isset($this->htmlParams['class'])) $this->htmlParams['class'] .= ' ' . $class; else $this->htmlParams['class'] = $class; }
Validation
Let's go ahead and add a validation mechanism to the class. The validation mechanism is, without a doubt, the most important part of the class.
Validation rules
We'll implement the validations by adding multiple validation rules to individual form controls. With that in mind, add the $rules array to the class:
private $rules = array();
The individual rules will either be a required-field rule, a regular-expression rule, a password rule, or other similar rule sets. After the form is submitted, every control's rules will be evaluated on both the client and server sides.
The individual rules or validators will be implemented in the FormControl class as simple methods. Another way to do it would be to inherit each rule into a separate class. However, these classes would be very short and similar, so we'll stick to the latter of the two approaches. We'll use the second approach to implement the individual form controls.
Constants
We'll distinguish individual rules using constants:
const RULE_REQUIRED = 0; const RULE_MAX_LENGTH = 1; const RULE_PASSWORD = 2; const RULE_DATETIME = 3; const RULE_PATTERN = 4; const RULE_REQUIRED_FILE = 5;
I'm sure you're able to tell what each rule checks. We can create more rules by combining these later.
Adding rules
Every rule will be represented as an associative array. We'll create a private method which adds them into the $rules array:
private function addRule($rule, $validateClient, $validateServer) { $rule['validate_client'] = $validateClient; $rule['validate_server'] = $validateServer; $this->rules[] = $rule; return $this; }
The method takes an array containing rule data. The other two parameters specify whether the rule should be evaluated on the client and server side. We add the properties based on the parameters and add the rule to the array. We also keep the setter approach and return the instance.
Required-field rule
Let's add a method which adds a required-field rule to a control:
public function addRequiredRule($validateClient = true, $validateServer = true) { return $this->addRule(array( 'type' => self::RULE_REQUIRED, 'message' => 'Required field', ), $validateClient, $validateServer); }
The method creates an array which represents the rule. Every rule contains a "type" key with a rule type and a "message" key with an error message. It will be displayed in case the rule is not satisfied, i.e. the field is empty or missing. We add the rule to the $rules array using the private method.
We'll create other methods which add other rules in the same way.
Maximal-length rule
The maximal-length rule is later translated to the maxlength attribute on the client-side. It'll look like the following:
public function addMaxLengthRule($maxLength, $validateClient = true, $validateServer = true) { return $this->addRule(array( 'type' => self::RULE_MAX_LENGTH, 'max_length' => $maxLength, 'message' => 'The maximal length of the value is ' . $maxLength, ), $validateClient, $validateServer); }
Regular-expression rule
The key rule is the rule which checks whether a field's content matches a regular expression.
public function addPatternRule($pattern, $validateClient = true, $validateServer = true) { return $this->addRule(array( 'type' => self::RULE_PATTERN, 'pattern' => $pattern, 'message' => 'The value has an invalid format', ), $validateClient, $validateServer); }
We'll use this rule very often in other rules. We'll add several constants for the most frequent regular expressions to the class:
const PATTERN_URL = '(http|https)://.*'; const PATTERN_INTEGER = '[0-9]+'; const PATTERN_EMAIL = '[a-z0-9._-]+@[a-z0-9.-]+\.[a-z]{2,4}$';
We'll define rules with a "^" character at the beginning and a "$" character at the end (we'll automate this process later on). In the next lesson, Finishing the FormControl class in PHP, we'll finish the class by adding methods for the remaining rules and their evaluation.