A standalone, expandable and a single-class validator for your user inputs. Good for those small projects where using a framework is too much.

Adding additional rules is easy. You add the error message to the protected $messages array defined at the top of the class, and if there is a strict mode to the rule you add a strict variant of your message into the protected $strictMessages defined above the protected $messages array. And then you set $this->messages['ruleName'] = $this->strictMessages['ruleName']. Set the key to the lowercase version of your rule you will be using in the validator rule list.

Let's say you want to add a rule named camelCase. You would have a message in $this->messages array with the key set to camelCase and than you would add a protected function named validateCamelCase($field, $arg, $arg2, $arg3) and then use that in your rules list like camelCase:arg,arg2,arg3. That's all it takes to add your own validation rule to this class.

<?php

class Validate
{
    protected array $input;
    protected array $rules;
    protected array $errors;
    public array $validated;
    protected $strictMessages = [
        'numeric'   => ':field must consist of only numbers.',
        'lowercase' => ':field must have all lowercase letters.',
        'uppercase' => ':field must have all uppercase letters.',
        'alpha'     => ':field must only consist of alphabetical characters.',
    ];
    protected $messages = [
        'required'  => ':field is required.',
        'email'     => 'The :field must be a valid email address.',
        'phone'     => ':field is not a valid phone number.',
        'min'       => ':field must be at least :min characters long.',
        'max'       => ':field must not exceed :max characters in length.',
        'unique'    => ':field already exists in our records.',
        'confirm'   => ':field does not match with the :confirm field.',
        'numeric'   => ':field must have numbers.',
        'lowercase' => ':field must have a lowercase letter',
        'uppercase' => ':field must have an uppercase letter.',
        'alpha'     => ':field must have a letter.',
        'mixed'     => ':field must contain a mix of numbers and letters.',
        'mixedCase' => ':field must consist of uppercase and lowercase letters.',
        'range'     => ':field must be between :min and :max characters in length.'
    ];

    // :field is replaced seperately.
    protected $placeholders = [':min', ':max', ':confirm'];

    protected $pattern = [
        'phone'     => '/^\(\d{3}\) \d{3}-\d{4}$/',
        'uppercase' => '/[A-Z]/',
        'lowercase' => '/[a-z]/',
        'alpha'     => '/[a-zA-Z]/',
        'numeric'   => '/[0-9]/',
    ];

    public function __construct(array $input, array $rules, array $customMessages = null, array $customStrictMessages = null)
    {
        $this->input = $input;
        $this->rules = $rules;
        $this->messages = $customMessages ?? $this->messages;
        $this->strictMessages = $customStrictMessages ?? $this->strictMessages;
    }

    public function validate() :bool
    {
        foreach ($this->rules as $field => $fieldRules) {
            $rules = explode('|', $fieldRules);
            $rules = array_filter($rules, 'strlen');

            foreach ($rules as $rule) {
                $this->applyRule($field, $rule);
            }
        }

        return empty($this->errors);
    }

    protected function applyRule(string $field, string $rule) :void
    {
        $params = [];

        if (strpos($rule, ':') !== false) {
            list($rule, $params) = explode(':', $rule, 2);
            $params = explode(',', $params);
        }

        $method = "validate" . ucfirst($rule);

        if (method_exists($this, $method)) {
            $value = $this->input[$field] ?? null;

            if (!$this->$method($field, ...$params) && (!in_array('nullable', explode('|', $this->rules[$field])) || !empty($value))) {
                $errorMessage = $this->getErrorMessage($field, $rule, $params);
                $this->errors[$field][] = $errorMessage;
            } else {
                if ($rule != 'confirm') {
                    $this->validated[$field] = $value;
                } else {
                    if (isset($this->validated[$field])) {
                        unset($this->validated[$field]);
                    }
                }
            }
        } else {
            throw new \InvalidArgumentException("Validation rule '{$rule}' not supported.");
        }
    }

    protected function validateNullable(string $field) :bool
    {
        return true;
    }

    protected function validateRequired(string $field) :bool
    {
        return isset($this->input[$field]) && trim($this->input[$field]) !== '';
    }

    protected function validateMin(string $field, int $minLength) :bool
    {
        return isset($this->input[$field]) && strlen($this->input[$field]) >= $minLength;
    }

    protected function validateMax(string $field, int $maxLength) :bool
    {
        return isset($this->input[$field]) && strlen($this->input[$field]) <= $maxLength;
    }

    protected function validateEmail(string $field) :bool
    {
        return isset($this->input[$field]) && filter_var($this->input[$field], FILTER_VALIDATE_EMAIL) !== false;
    }

    protected function validateConfirm(string $field, string $confirmationField) :bool
    {
        return isset($this->input[$field]) && isset($this->input[$confirmationField]) &&
            $this->input[$field] === $this->input[$confirmationField];
    }

    protected function validateUnique(string $field, string $modelName) :bool
    {
        $model = "App\\Models\\" . ucfirst($modelName);
        return (new $model)->unique($field, $this->input[$field]);
    }

    protected function validatePhone(string $field) :bool
    {
        // Return the result of the pattern match
        return preg_match($this->pattern['phone'], $this->input[$field]);
    }

    protected function validateNumeric(string $field, bool $strict = false) :bool
    {
        if ($strict) {
            $this->messages['numeric'] = $this->strictMessages['numeric'];
            return ctype_digit($this->input[$field]);
        }

        return preg_match($this->pattern['numeric'], $this->input[$field]) > 0;
    }

    protected function validateAlpha(string $field, bool $strict = false) :bool
    {
        if ($strict) {
            $this->messages['alpha'] = $this->strictMessages['alpha'];
            return ctype_alpha($this->input[$field]);
        }

        return preg_match($this->pattern['alpha'], $this->input[$field]) > 0;
    }

    protected function validateMixed(string $field) :bool
    {
        return ($this->validateNumeric($field) && $this->validateAlpha($field));
    }

    protected function validateUppercase(string $field, bool $strict = false) :bool
    {
        if ($strict) {
            $this->messages['uppercase'] = $this->strictMessages['uppercase'];
            return ctype_upper(str_replace(' ', '', $this->input[$field]));
        }

        return preg_match($this->pattern['uppercase'], $this->input[$field]) > 0;
    }

    protected function validateLowercase(string $field, bool $strict = false) :bool
    {
        if ($strict) {
            $this->messages['lowercase'] = $this->strictMessages['lowercase'];
            return ctype_lower(str_replace(' ', '', $this->input[$field]));
        }
        return preg_match($this->pattern['lowercase'], $this->input[$field]) > 0;
    }

    protected function validateMixedCase(string $field) :bool
    {
        return ($this->validateUppercase($field) && $this->validateLowercase($field));
    }

    protected function validateRange(string $field, int $min, int $max) :bool
    {
        $length = strlen($this->input[$field]);
        return ($length >= $min && $length <= $max);
    }

    protected function getErrorMessage(string $field, string $rule, array $params) :string
    {
        $message = $this->messages[$rule] ?? "Validation failed for rule '{$rule}'";

        foreach ($params as $param) {
            foreach ($this->placeholders as $placeholder) {
                if (strpos($message, $placeholder)) {
                    $message = str_replace($placeholder, $param, $message);
                    break; // Break out of the inner loop once a placeholder is replaced
                }
            }
        }

        return str_replace(':field', ucwords(str_replace('_', ' ', $field)), $message);
    }

    public function getErrors() :array
    {
        return $this->errors;
    }

    public function validated() :array
    {
        return $this->validated;
    }
}

This code snippet was published on It was last edited on

Contributing Authors

1

1 Comment

  • Votes
  • Oldest
  • Latest
BO
443 9
Commented
Updated

A simple use of this validator using most of the rules already provided in this class. Only rule not used in this example is unique and the method for it in the class (validateUnique) is not implemented because it requires you to connect to the database and check the database if the user-provided input exists in the database to make sure you don't add second instance of an email address (or whatever) that already exists in the database.

I coded the unique to take only one parameters (after the field name) to tell the method which table to check for the input. If you want to add a second param to tell the script which column to check (if it's different than the input name) than you could add the $column into the method argument list (validateUnique($field, $modelName, $column)) and use it in the rules like unique:table,column.

The class has a 'nullable' rule which allows for an optional field that has validation if it has any value filled in.

$input = [
    'username'          => 'Some Name',
    'email'             => 'sample@email.com',
    'password'          => '12345678dF',
    'password_confirm'  => '12345678dF',
    'phone'             => '(123) 456-7890',
    'phoneAlt'          => '1234567890',
    'word'              => 'alphabet',
    'anotherword'       => 'FGHJ',
    'optional'          => ''
];

// Data for the new user
$validator = new Validate($input, [
    'username'          => 'required|min:4|alpha|uppercase',
    'email'             => 'required|email',
    'password'          => 'required|mixed|mixedCase|range:4,15',
    'password_confirm'  => 'required|confirm:password',
    'phone'             => 'phone',
    'phoneAlt'          => 'min:10|max:10|numeric',
    'word'              => 'alpha:strict|lowercase:strict',
    'anotherword'       => 'uppercase:strict',
    'optional'          => 'nullable|email'
]);

/*
    * 'unique:users' is also a rule, making sure a column by the
    *  name of the input name is unique in the table named 'users'
    *  in this example. You could obviously put a different table
    *  name in place of users.
**/

echo "<pre>\n";
if ($validator->validate()) {
    print_r($validator->validated());
} else {
    // Validation failed
    $errors = $validator->getErrors();
    print_r($errors);
}
echo "</pre>\n";
add a comment
1