Creating a perfect form base class for Laravel

Forms, they are one of the basic part of any web application. I have to agree they are the most boring parts of web development. We have to create the html fields, validate when user submit the data and if there is any validation issues show the error, then sanitize the data save them into database. This is ever repeating process. Laravel has helped to reduce these tasks to minimal level using validation methods. But still we don’t have a perfect way to do common things that we do with the forms.

Basic things that are included in the form flow

  1. Validation
    Validation validates the form data and returns the error messages, Laravel validators does this job beautifully.
  2. Modification
    We modify the form data to either create a new field or to make them into a database acceptable form. Like if we have three fields for date of birth, day,year and month we combine them to form dob field. we usually does this job in model/service or sometimes in controller.
  3. Discarding the extra
    Some times we need to discard some data that are not needed to save to the database, we usually does this manually in controller/model. In the date of birth example we don’t need day,year,month after we have formed the dob field. Or we don’t need password confirmation field after we have verified the password field.

What I am trying to do is to move this three jobs to a form class, such that we create separate classes for each form. Jeffrey Way from Laracast has done a really nice job for this by creating the Laracast validation package.

Laracast validation package essentially solves the 1st part of our issue separating validation from model/controller to a separate class but doesn’t solve the modification and discarding the data parts. So let us try to extend the laracast validation class to make a perfect form class.

Please check the laracast validation package documentation before proceeding.

We create a base form class that we extend for other forms just like you do with the BaseController class.

Our BaseForm class will extend the Laracast Validation and then we add the extra functionalities required.

I hope that you have installed laracast form validation already. Let us start creating the base form

<?php
//BaseForm.php
use Laracasts\Validation\FormValidator;
use Laracasts\Validation\FormValidationException;

/**
 * Base form
 */
class BaseForm extends FormValidator {
    
}

Now that we have extended FormValidator we have the validate method which validate the input data based on the $rules array but FormValidator doesnt store form data in the class so let us over ride this function and make some change.

<?php
//BaseForm.php
use Laracasts\Validation\FormValidator;
use Laracasts\Validation\FormValidationException;

/**
 * Base form
 */
class BaseForm extends FormValidator {

    protected $formData;

    public function validate(array $formData) {
        $this->formData = $formData;// we are saving form data to $formData class variable
        $this->validation = $this->validator->make(
                $formData, $this->getValidationRules(), $this->getValidationMessages()
        );

        if ($this->validation->fails()) {
            throw new FormValidationException('Validation failed', $this->getValidationErrors());
        }

        return true;
    }

}

What we have modified from original laracast source is we added class variable $formData and when form data is passed to validate method we set this data to $formData

Now we have completed the first part of form flow validating the form.

Now let us move to second and third part modifying the form data and getting only required data.
We define another class variable called $requiredFields which will have keys of required fields, we create an empty protected function modify() which will be overridden in the real form class to modify the form data and then we have another function getRequiredData() which returns required data after making modification. So below is the final class.

<?php
//BaseForm.php
use Laracasts\Validation\FormValidator;
use Laracasts\Validation\FormValidationException;

/**
 * Base form
 */
class BaseForm extends FormValidator {
    /**
     * Form data
     * @var array 
     */
    protected $formData = [];
    /**
     * Required fields that will be return on getRequiredData function 
     * @var array 
     */
    protected $requiredFields = [];
    /**
     * Custom validation messages
     * @var array 
     */
    protected $messages = [];
    /**
     * Validation rules for each field
     * @var array
     */
    protected $rules = [];
    /**
     * Validate the form data
     * 
     * @param array $formData
     * @return boolean
     * @throws FormValidationException
     */
    public function validate(array $formData) {
        $this->formData = $formData; //setting to class variable for later use
        $this->validation = $this->validator->make(
                $formData, $this->getValidationRules(), $this->getValidationMessages()
        );

        if ($this->validation->fails()) {
            throw new FormValidationException('Validation failed', $this->getValidationErrors());
        }

        return true;
    }
    /**
     * Modify the form data, override this function in child classes to modify the form data
     */
    protected function modify() {
        
    }
    /**
     * Get required data
     * @return array
     */
    public function requiredData() {
        /**
         * Modify the form data
         */
        $this->modify();
        /**
         * Filter only required form fields using the required fields array
         */
        return array_only($this->formData, $this->requiredFields);
    }

}

Using BaseForm Class
Let us create an example registration form that has the following fields
first_name,last_name,email,password,password_confirmation,dob_day,dob_year,dob_month.
So we create RegisterForm class by extending BaseForm

<?php
//RegistrationForm.php
class RegistrationForm extends BaseForm {

    protected $rules = [
        'first_name' => 'required|alpha',
        'last_name' => 'required|alphs',
        'email' => 'required|email',
        'password' => 'required|confirmed',
        'password_confirmation' => 'required|same:password',
        'day' => 'required|int',
        'year' => 'required|int',
        'month' => 'required|int'
    ];
    protected $requiredFields = [
        'first_name',
        'last_name',
        'email',
        'password',
        'dob'
    ];

    protected function modify() {
        /**
         * Create new dob field
         */
        $this->formData['dob'] = $this->formData['year'] . "-" . $this->formData['month'] . $this->formData['day'];
    }

}

Our controller will look like this, I have ignored the model function which you will have to create also I hope you didn’t forget to autoload the forms using composer.

<?php
//UserController.php
use Laracasts\Validation\FormValidationException;

class UserController extends BaseController {

    protected $registrationForm;

    public function __construct(RegistrationForm $registrationForm, User $user) {
        $this->registrationForm = $registrationForm;
    }
    //show the registration form
    public function getRegister() {
        return View::make('user.register');
    }
    //process user registration
    public function postRegister() {
        try {
            $this->registrationForm->validate(Input::all());
            $user = $this->user->register($user);
            return Redirect::action('HomeController@getIndex')
                            ->with('success', $user->first_name . " thank you, your account is created. Please verify your email address by following instructions we have sent your email.");
        } catch (FormValidationException $e) {
            return Redirect::back()
                            ->withInput()
                            ->withErrors($e->getErrors())
                            ->with('error', 'Please correct the form errors and submit again!');
        }
    }

}



Conclusion

By creating the BaseForm class and creating child form classes we can affectively move the form logic from controller and model to its own classes. This create a lot more flexibility when creating your laravel application.