View code
Rebuilding Laravel’s Validation System: Part 2
In the previous part, we created the blueprint for the validation system we want to build. We created the Request class and the Rule interface. We also created some predefined rules like Required, Boolean, EmailAddress, and Numeric. In this part, we will see how to use these rules in the Request class to validate the data.
The Validator class
The Request class is responsible for connecting the data with the rules and performing the validation. To do this, we will create a Validator class that will receive the data, the rules, and the validation rules and perform the validation.
<?phpdeclare(strict_types=1);namespace Devlob\Validation;use Devlob\Validation\Rules\Boolean;use Devlob\Validation\Rules\EmailAddress;use Devlob\Validation\Rules\Numeric;use Devlob\Validation\Rules\Required;use Exception;use FilesystemIterator;class Validator{ private array $data = []; private array $rules = []; private array $validation = []; public function __construct() { $this->setupPredefinedRules(); } private function setupPredefinedRules(): void { $predefinedRules = [Required::class, Numeric::class, EmailAddress::class, Boolean::class]; foreach ($predefinedRules as $rule) { $class = new $rule(); $this->rules[$class->key()] = $rule; } } public function make(array $data, array $validation): self { $this->data = $data; $this->validation = $validation; return $this; } public function validate(): array { $errors = []; // Loop through the validation rules and validate the data return $errors; }}When we create a new instance of the Validator class, we call the setupPredefinedRules method to load the predefined
rules we created in the previous part. The method simply creates an instance of each rule and stores it in the $rules
array as a key-value pair where the key is the rule key and the value is the rule class.
If you var_dump the $rules array once the predefined rules are loaded, you will see something like this:
{"required":"Devlob\\Validation\\Rules\\Required","numeric":"Devlob\\Validation\\Rules\\Numeric","email_address":"Devlob\\Validation\\Rules\\EmailAddress","boolean":"Devlob\\Validation\\Rules\\Boolean"}The make method is responsible for setting the data and the validation rules. The validate method is responsible for
validating the data against the rules and returning an array of errors if any.
Validating the data
public function validate(): array{ $errors = []; foreach ($this->validation as $attribute => $validationRules) { $validationRules = explode('|', $validationRules); foreach ($validationRules as $validationRule) { if (isset($this->rules[$validationRule])) { $class = new $this->rules[$validationRule](); $validate = $class->passes($attribute, $this->data[$attribute]); if (!$validate) { $errors[$attribute] = str_replace(':attribute', $attribute, $class->message()); } } else { throw new Exception("Rule '$validationRule' is not defined."); } } } return $errors;}We start by creating an empty array called $errors to store the errors. We loop through the validation rules and
explode them by the pipe character |. We then loop through each rule and check if the rule is defined in the $rules
array. If it is, we create an instance of the rule class and call the passes method to validate the data. If the data
does not pass the validation, we store the error message in the $errors array using the attribute name as the key and
replace the :attribute placeholder in the error message with the attribute name.
If the rule is not defined in the $rules array, we throw an exception, which makes sense because we should not have
undefined rules in the validation rules.
Using the Validator class
Now that we have the Validator class, we can use it in the Request class to validate the data. Let's update the Request class to use the Validator class.
Update the validate method in the Request class:
public function validate(array $rules): string|bool{ $errors = $this->validator ->make($this->data, $rules) ->validate(); if ($errors) { return json_encode([ 'errors' => $errors, 'message' => 'Validation failed.' ], 422); } return true;}Now, when we call the validate method on the Request class, it will use the Validator class to validate the data
against the rules and return the errors if any.
Testing our validation system
Let's test our validation system by creating a new Request class and using it to validate some data.
<?phpdeclare(strict_types=1);require __DIR__ . '/vendor/autoload.php';use Devlob\Request\Request;$request = Request::create([ 'name' => '', 'age' => 'a', 'email' => 'renato@', 'is_admin' => 'not_a_boolean',]);echo $request->validate([ 'name' => 'not_empty', 'age' => 'numeric', 'email' => 'required|email_address', 'is_admin' => 'boolean',]);If you run the code above, you should see the following output:
{ "errors": { "name": "The name field should not be empty.", "age": "The age field must be numeric.", "email": "The email field must be a valid email.", "is_admin": "The is_admin field must be a boolean." }, "message": "Validation failed."}The validation system is working as expected. We can now validate data using the predefined rules we created. Let's update the data and see if the validation passes.
<?phpdeclare(strict_types=1);require __DIR__ . '/vendor/autoload.php';use Devlob\Request\Request;$request = Request::create([ 'name' => 'Renato Hysa', 'age' => '31', 'email' => 'renato@hysa.dev', 'is_admin' => true,]);echo $request->validate([ 'name' => 'not_empty', 'age' => 'numeric', 'email' => 'required|email_address', 'is_admin' => 'boolean',]);If you run the code above, you should see 1 as the output, which means the validation passed.
Coming up next
In the next part, we will see how to create custom rules and how to use them in the validation system.