CodeIgniter Forums
Own validation rules with CodeIgniter >4.5 - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Using CodeIgniter (https://forum.codeigniter.com/forumdisplay.php?fid=5)
+--- Forum: Best Practices (https://forum.codeigniter.com/forumdisplay.php?fid=12)
+--- Thread: Own validation rules with CodeIgniter >4.5 (/showthread.php?tid=92432)

Pages: 1 2


Own validation rules with CodeIgniter >4.5 - minsk832 - 02-05-2025

Based on the CodeIgniter documentation on validation, there are still a few things that are a bit unclear to me, so I wanted to ask here for advice on whether my approach is in line with best practice and how you would implement it.

https://codeigniter.com/user_guide/libraries/validation.html

I have created a new class under app/Validation/NewsletterRules.php:

PHP Code:
<?php

namespace App\Validation;

class 
NewsletterRules
{
    // Validation rules
    public static $rules = [
        'email' => 'required|valid_email|max_length[254]|isNotTrashmail|hasValidMxRecord',
        //...
    ];

    private function getEmailHost(string $email): string
    
{
        $parts explode('@'$email);
        return strtolower(trim(end($parts)));
    }

    public function isNotTrashmail(string $str null, ?string &$error null): bool
    
{
        $trashmailFilePath WRITEPATH 'data/trashmails.txt';
        if (!file_exists($trashmailFilePath)) {
            return true;
        }

        $trashmailList file($trashmailFilePathFILE_IGNORE_NEW_LINES FILE_SKIP_EMPTY_LINES);
        $emailHost $this->getEmailHost($str);

        foreach ($trashmailList as $trashDomain) {
            if (stripos($emailHost$trashDomain) !== false) {
                $error 'Email provider not supported.';
                return false;
            }
        }

        return true;
    }

    public function hasValidMxRecord(string $str null, ?string &$error null): bool
    
{
        $host $this->getEmailHost($str);
        if (!checkdnsrr($host'MX')) {
            $error 'The domain does not have a valid DNS MX record.';
            return false;
        }
        return true;
    }


And registered them accordingly under app/Config/Validation.php:

PHP Code:
    public array $ruleSets = [
        //...,
        \App\Validation\NewsletterRules::class,
        //...
    ]; 

Now I would do the following in the controller:

PHP Code:
$validation service('validation');
$validation->setRules(\App\Validation\NewsletterRules::$rules);
if (
$validation->run($data)) {
    $validatedData $validation->getValidated();


Would this be the correct / best possible procedure? Are there more elegant ways? Do you prefer the validation rules in the controller or in the model? In general, most frameworks seem to leave the validation completely to the controller?

I am also a bit confused by the statement at $validation = service('validation'); What exactly does this mean? Isn't it used in the example itself?

Quote:"You may never need to use this method, as both the Controller and the Model provide methods to make validation even easier."


Thank you very much for your thoughts!


RE: Own validation rules with CodeIgniter >4.5 - ozornick - 02-05-2025

All good.
See example https://github.com/neznaika0/codeigniter-expenses/blob/6a7fefef3a3974dec84899faba6ac05909f11283/app/Controllers/Expense.php#L46 and ExpenseRules.
Before validation in model allows you to provide errors earlier. And the adoption in the class is better than the group in Config/Validation or the hard code in the controller. 
I am glad that someone improves their skills with the framework Smile


RE: Own validation rules with CodeIgniter >4.5 - minsk832 - 02-05-2025

@ozornick
Thanks for the feedback! What exactly do you mean by ExpenseRules? Your link leads to this post Smile


RE: Own validation rules with CodeIgniter >4.5 - grimpirate - 02-05-2025

You've modified your $rulesets array to use Traditional Rules, which the documentation say is a no-no.

"Traditional Rules exist only for backward compatibility. Do not use them in new projects. Even if you are already using them, we recommend switching to Strict Rules." Without surveying your code the difference between Strict/Traditional is that strict rules also check the type of whatever they're validating.

Ozornick meant for you to look at the linked project in his signature which has a ValidationRules\ExpenseRules class which encapsulates an array of the validation rules provided by CodeIgniter. Mostly prevents code clutter in your Controllers/Models.

In your case that wouldn't work because you have custom logic that is doing something outside the scope of the standard rules (which are mostly meant to validate data that is input via a form). From your class names and general logic I surmise you're attempting to determine which emails are good for a newsletter signup by eliminating those from a blacklist and making sure that a submitted email has an MX record.

Your code seems fine, but I would use the validateData method rather than setRules and then run. If I were going to use run I would also do away with setRules and just created a named rule group (since you've gone through the trouble of creating a Validation class), remove the static $rules property from your Newsletter class and then execute as run($data, 'newsletter'). Read How to Save Your Rules and How to Specify Rule Group.


RE: Own validation rules with CodeIgniter >4.5 - ozornick - 02-05-2025

Oh, sorry. I updated post, see example https://github.com/neznaika0/codeigniter-expenses/blob/6a7fefef3a3974dec84899faba6ac05909f11283/app/Controllers/Expense.php#L46


RE: Own validation rules with CodeIgniter >4.5 - minsk832 - 02-07-2025

Thank you very much for your feedback. To be honest, I'm still a bit in the fog, although I have looked at the code and the documentation again.

Quote:You've modified your $rulesets array to use Traditional Rules, which the documentation say is a no-no.

My $ruleSets array:

PHP Code:
    public array $ruleSets = [
        Rules::class,
        \App\Validation\NewsletterRules::class,
        FormatRules::class,
        FileRules::class,
        CreditCardRules::class,
    ]; 

But the documentary says:
https://codeigniter.com/user_guide/libraries/validation.html#traditional-and-strict-rules

Quote:Since v4.3.0, Strict Rules are used by default for better security.

And you can also see above:

PHP Code:
use CodeIgniter\Validation\StrictRules\CreditCardRules;
use 
CodeIgniter\Validation\StrictRules\FileRules;
use 
CodeIgniter\Validation\StrictRules\FormatRules;
use 
CodeIgniter\Validation\StrictRules\Rules

Maybe I haven't really understood what $ruleSets is used for in the system. My understanding was: If I don't register the class there, the system can't do anything with rules like hasValidMxRecord.

In the example code, however, I don't see it registered in $ruleSets and it's called

PHP Code:
if ($this->validateData($inputExpenseRules::create()) === false) {
 
    return redirect()->back()->withInput()->with('validation'$this->validator->getErrors());


instead of

PHP Code:
$validation service('validation');
$validation->setRules(NewsletterRules::$rules);
if (!
$validation->run($insertData)) {
    return $this->response->setJSON([
 
        'status' => 'error',
 
        'code' => 'validation_failed',
 
        'message' => implode(', '$validation->getErrors()),
 
    ]);


But from the documentation I primarily see the path via $validation->setRules().

Where am I missing the connection or understanding? What exactly in my code is causing me to not follow the strict recommendation in the documentation?


RE: Own validation rules with CodeIgniter >4.5 - grimpirate - 02-07-2025

I apologize minsk832 my eyes played tricks on me, you are in fact using the strict rules and adding your own into it.

Your $rulesets declaration is fine, but since you're already modifying the app/Config/Validation.php file I think you would benefit from the following addition to it:
PHP Code:
<?php
namespace Config;
// ...
class Validation extends BaseConfig
{
    // ...
    public array $ruleSets = [
        Rules::class,
        \App\Validation\NewsletterRules::class,
        FormatRules::class,
        FileRules::class,
        CreditCardRules::class,
    ]; 
    // ...
    public array $newsletter = [
        'email'        => 'required|max_length[254]|valid_email|isNotTrashmail|hasValidMxRecord',
    ];
    // ...

Your NewsLetterRules class becomes:
PHP Code:
<?php

namespace App\Validation;

class 
NewsletterRules
{
    private function getEmailHost(string $email): string
    
{
        $parts explode('@'$email);
        return strtolower(trim(end($parts)));
    }

    public function isNotTrashmail(string $str null, ?string &$error null): bool
    
{
        $trashmailFilePath WRITEPATH 'data/trashmails.txt';
        if (!file_exists($trashmailFilePath)) {
            return true;
        }

        $trashmailList file($trashmailFilePathFILE_IGNORE_NEW_LINES FILE_SKIP_EMPTY_LINES);
        $emailHost $this->getEmailHost($str);

        foreach ($trashmailList as $trashDomain) {
            if (stripos($emailHost$trashDomain) !== false) {
                $error lang('Errors.email.unsupported');
                return false;
            }
        }

        return true;
    }

    public function hasValidMxRecord(string $str null, ?string &$error null): bool
    
{
        $host $this->getEmailHost($str);
        if (!checkdnsrr($host'MX')) {
            $error lang('Errors.email.mx');
            return false;
        }
        return true;
    }

A language file in Language/en/Errors.php (good practice to always use lang for your strings to save yourself future headaches):
PHP Code:
<?php

return [
    'email' => [
        'unsupported' => 'Email provider not supported.',
        'mx' => 'The domain does not have a valid DNS MX record.',
    ],
]; 
And finally, your validation code in your controller becomes:
PHP Code:
if ($this->validator->run($data'newsletter')) {
    $validatedData $this->validator->getValidated();


Anyways, sorry for all the confusion, hope it helps.


RE: Own validation rules with CodeIgniter >4.5 - ozornick - 02-08-2025

I do not recommend using groups $newsletter in Validation.  Just think that you have >100 rules, you will get lost in them. I discovered this before I had done half of the project. 

Use separate arrays - I've taken out their classes. 
$ruleSets don't need to be updated, look inside Expense::create() - there are a set of rules inside, not a custom check. You also need to add a handler NewsletterRules to $ruleSets, and put the rule set anywhere else. 

You saved them in public static $rules, that's fine. You just need to work with the name for different cases: $create $update...


RE: Own validation rules with CodeIgniter >4.5 - grimpirate - 02-08-2025

I neglected to answer one of your original questions which was regarding your confusion about $validation = service('validation');

Validation in CodeIgniter is provided as a Service. It's an abstraction that acts as a factory service to help you generate instances of the Validation class. You could just as easily do new CodeIgniter\Validation\Validation() but you would have to pass configuration parameters to the constructor every time which would be annoying, or you would have to create some other class to do the configuration for you and return a validation object i.e. the service() function does that for you. It's a convenience because it happens frequently enough to be justified. You should only need a specific configuration validator in very few instances.

Furthermore, you don't need to instantiate the validation service if you're already inside a Controller because CodeIgniter also gives you a validator object in one of the BaseController's default properties i.e. $this->validator.

Lastly, I disagree with ozornick's recommendation. CodeIgniter gives you enough freedom to do things however you like (caveat emptor), and while I understand his logic in terms of creating classes with static methods that return arrays of rules, the object he created is not a general "best practice," it's his own particular style/take/approach. Validation rules are a matter of configuration (as defined by the framework). CodeIgniter implements an extended configuration mechanism. Validation rules belong alongside configuration. I can agree that you may not want 100 rules inside of your Config/Validation.php file, but that's why CodeIgniter allows you to create implicit/explicit Registrars. You could readily create distinct registrars for each rule group you want and it's the equivalent technique that ozornick is using, except that when another programmer views your code they will understand the relationship to configuration. Whereas in ozornick's example you have an nebulous class which acts as an encapsulator for static methods, the object becomes irrelevant, and its purpose is not clear when you look at its source unless you have forehand knowledge of how validation works in CodeIgniter. You're essentially creating grouped functions, almost like what a helper is and therefore should be a series of helper functions as calling the object is unnecessary (or more particularly an Enum as these rules are primarily constants). This would be fine in a solely object-oriented language like... Java where you have something like a Math library that only operates on numbers and therefore doesn't require an object, but even then you could still instantiate a Math() object which has constants like Pi or some other such and then use its methods. I'm being pedantic at this point, but that's what "best practice" constitutes: an attempt to remove the subjective "works for me" to a "everybody would understand this because it follows the patterns established by the framework."


RE: Own validation rules with CodeIgniter >4.5 - ozornick - 02-08-2025

Registrars are a bad idea. It works in a limited way and mostly with modules.

I would like to remind you that some people don't use anything: neither groups nor classes. They create them in controllers  Smile

You are wrong about $this‐>validator, the object will appear only after validation, so you can’t work with it before that.