DEV Community

Cover image for PHP - Create your own Data Validator in PHP: Step-by-Step
F.R Michel
F.R Michel

Posted on

PHP - Create your own Data Validator in PHP: Step-by-Step

How to Create Your Own Data Validator in PHP

In this tutorial, I will teach you how to create a custom data validator in PHP by building our own validation library step by step. Data validators are essential tools for any developer who needs to ensure that user-submitted data is valid and secure. By the end of this tutorial, you will have a solid understanding of how to create custom data validators in PHP, which will allow you to better handle user inputs and ensure the security of your applications.

Step 1 : Creating the Validation Class

The first step is to create a class that will handle the validation. This class should be able to store the validation rules for each field that we want to validate, as well as validate those rules when called.

Here is an example of a simple validation class:

<?php

namespace DevCoder\Validator;

use DevCoder\Validator\Assert\ValidatorInterface;
use InvalidArgumentException;
use function get_class;
use function gettype;
use function is_array;
use function is_object;
use function sprintf;

class Validation
{
    /**
     * @var array<string,array>
     */
    private $validators;

    /**
     * @var array<string,string>
     */
    private $errors = [];

    /**
     * @var array
     */
    private $data = [];

    public function __construct(array $fieldValidators)
    {
        foreach ($fieldValidators as $field => $validators) {
            if (!is_array($validators)) {
                $validators = [$validators];
            }
            $this->addValidator($field, $validators);
        }
    }

    public function validate(array $data): bool
    {
        $this->data = $data;

        /**
         * @var $validators array<ValidatorInterface>
         */
        foreach ($this->validators as $field => $validators) {
            if (!isset($this->data[$field])) {
                $this->data[$field] = null;
            }

            foreach ($validators as $validator) {
                if ($validator->validate($this->data[$field]) === false) {
                    $this->addError($field, (string)$validator->getError());
                }
            }

        }
        return $this->getErrors() === [];
    }

    /**
     * @return array<string,string>
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /**
     * @return array
     */
    public function getData(): array
    {
        return $this->data;
    }

    private function addError(string $field, string $message): void
    {
        $this->errors[$field][] = $message;
    }

    /**
     * @param string $field
     * @param array<ValidatorInterface> $validators
     * @return void
     */
    private function addValidator(string $field, array $validators): void
    {
        foreach ($validators as $validator) {
            if (!$validator instanceof ValidatorInterface) {
                throw new InvalidArgumentException(sprintf(
                    $field . ' validator must be an instance of ValidatorInterface, "%s" given.',
                    is_object($validator) ? get_class($validator) : gettype($validator)
                ));
            }

            $this->validators[$field][] = $validator;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Creating rule classes for data validation

Now that we have created the Validator class, the next step is to create our own validation rules. These rules will be used to check if the submitted data is valid or not. We will create them in separate files, one for each validation rule. Each validation rule file should contain a class named after the rule it implements. For example, if we have a validation rule to check if a value is an integer, we will name the class Integer.

ValidatorInterface

<?php

namespace DevCoder\Validator\Assert;

interface ValidatorInterface
{
    public function validate($value): bool;
    public function getError(): ?string;
}
Enter fullscreen mode Exit fullscreen mode

AbstractValidator

<?php

namespace DevCoder\Validator\Assert;

abstract class AbstractValidator implements ValidatorInterface
{
    /**
     * @var string|null
     */
    protected $error;

    public function getError(): ?string
    {
        return $this->error;
    }

    protected function error(string $message, array $context): void
    {
        $replace = [];
        foreach ($context as $key => $value) {
            if (is_object($value)) {
                $value = method_exists($value, '__toString') ? (string)$value : get_class($value);
            } elseif (is_array($value)) {
                $value = json_encode($value);
            } else {
                $value = (string)$value;
            }
            $replace['{{ ' . $key . ' }}'] = $value;
        }

        $this->error = strtr($message, $replace);
    }
}
Enter fullscreen mode Exit fullscreen mode

Integer

<?php

declare(strict_types=1);

namespace DevCoder\Validator\Assert;

use function ctype_digit;
use function is_int;
use function strval;

class Integer extends AbstractValidator
{
    /**
     * @var string
     */
    private $invalidMessage = 'This value should be of type {{ type }}.';
    private $minMessage = '{{ value }} should be {{ limit }} or more.';
    private $maxMessage = '{{ value }} should be {{ limit }} or less.';

    /**
     * @var int|null
     */
    private $min;
    /**
     * @var int|null
     */
    private $max;

    public function validate($value): bool
    {
        if ($value === null) {
            return true;
        }

        if (ctype_digit(strval($value)) === false) {
            $this->error($this->invalidMessage, ['value' => $value, 'type' => 'integer']);
            return false;
        }

        if (is_int($this->min) && $value < $this->min) {
            $this->error($this->minMessage, ['value' => $value, 'limit' => $this->min]);
            return false;
        }

        if (is_int($this->max) && $value > $this->max) {
            $this->error($this->maxMessage, ['value' => $value, 'limit' => $this->max]);
            return false;
        }

        return true;
    }

    public function invalidMessage(string $invalidMessage): self
    {
        $this->invalidMessage = $invalidMessage;
        return $this;
    }

    public function minMessage(string $minMessage): self
    {
        $this->minMessage = $minMessage;
        return $this;
    }

    public function maxMessage(string $maxMessage): self
    {
        $this->maxMessage = $maxMessage;
        return $this;
    }

    public function min(int $min): self
    {
        $this->min = $min;
        return $this;
    }

    public function max(int $max): self
    {
        $this->max = $max;
        return $this;
    }
}

Enter fullscreen mode Exit fullscreen mode

NotNull

<?php

declare(strict_types=1);

namespace DevCoder\Validator\Assert;

class NotNull extends AbstractValidator
{
    private $message = 'This value should not be null.';

    public function validate($value): bool
    {
        if ($value === null) {
            $this->error($this->message, ['value' => $value]);
            return false;
        }

        return true;
    }

    public function message(string $message): self
    {
        $this->message = $message;
        return $this;
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 3: Creating an instance of the Validation class

This object takes an array of validation options as input. The keys of the array are the names of the fields and the values are arrays of validators.

<?php
$validation = new Validator([
    'age' => [(new Integer())->min(18)->max(99), new NotNull()],
    'number_of_children' => [new NotNull(), new Integer()],
    'salary' => [new NotNull(), new Integer()],
]);
Enter fullscreen mode Exit fullscreen mode

Step 4: Data Validation

Once you have created an instance of the Validation class, you can validate the data by calling the validate() method of the Validation class. This method will return true if all validation rules are satisfied and false otherwise.

<?php

if ($validation->validate($_POST) === true) {
    $data = $validation->getData();
    // save in database
    // redirect in another page
}

return render('template.html.php', [
    'errors' => $validation->getErrors()
]);
Enter fullscreen mode Exit fullscreen mode

Example of other rules that can be added

$validation = new Validation([
    'email' => [new NotNull(), new Email()],
    'password' => new NotNull(),
    'firstname' => [new NotNull(), (new StringLength())->min(3), new Alphabetic()],
    'lastname' => [(new StringLength())->min(3)],
    'gender' => new Choice(['Mme', 'Mr', null]),
    'website' => [new NotNull(), new Url()],
    'age' => [new NotNull(), (new Integer())->min(18)],
    'invoice_total' => [new NotNull(), new Numeric()],
    'active' => [new NotNull(), new Custom(function ($value) {
        return is_bool($value);
    })]
]);
Enter fullscreen mode Exit fullscreen mode

To see other validation rules that can be added, check out my GitHub repository at the following URL: https://github.com/devcoder-xyz/php-validator/tree/main/src/Assert

Ideal for small project
Simple and easy!
https://github.com/devcoder-xyz/php-validator

Top comments (6)

Collapse
 
nicolasdanelon profile image
Nicolás Danelón

I just love it 👌🏼 super clear

Collapse
 
fadymr profile image
F.R Michel

Thanks you

Collapse
 
karim_abdallah profile image
Karim Abdallah

What a post ♥️

Collapse
 
fadymr profile image
F.R Michel

Thanks you

Collapse
 
yigitsayan profile image
Yigit Sayan

Clean, readable and maintainable code, well done!

Collapse
 
fadymr profile image
F.R Michel

Thanks you