Give name to custom validation Rule - Laravel 8
Asked Answered
L

4

8

Laravel 8 makes it possible to create custom Validation rules: https://laravel.com/docs/8.x/validation#custom-validation-rules

php artisan make:rule Euro

But then you have to pass the rule as a object (instead of a string):

new Euro

instead of the regular string notation

'required|euro'

Is there anyway to "register" the new Rule classes to a string identifier and use them like you can you the pre-existing rules?

Lumbricalis answered 25/8, 2021 at 11:29 Comment(2)
you know that it would not change a thing (just make it slower) to achieve this. The string will be used to initiate the rule class and then used.Olivia
There are circumstances where you can't specify validation rules using class instances. Therefore it is a valid question.Gnu
G
9

You can do it the following way.

Create two additional methods in your rule, besides the default ones (passes(), message()):

  • handle() -- This one will return your rule's string handle. It's sole purpose is to keep track of that handle in a single place.
  • validate() -- That's the one which will be invoked by the validator. This should essentially be just a pass through to passes() so you can keep your validation logic in one place. Additionally you should pass the message into the validator. In most cases you also want to keep the message logic in one place.

use Illuminate\Validation\Validator;

public static function handle(): string
{
    return 'euro';
}


public function validate(string $attribute, $value, $params, Validator $validator): bool
{
    $handle = $this->handle();


    $validator->setCustomMessages([
        $handle => $this->message(),
    ]);

    return $this->passes($attribute, $value);
}

Register your class using the Validator facade or the factory in a Service Provider's boot() method:


namespace App\Providers;

use App\Rules\Euro;
use Illuminate\Contracts\Validation\Factory as ValidatorFactory;
use Illuminate\Support\ServiceProvider;

class ValidationServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot(ValidatorFactory $validator)
    {
        $validator->extend(Euro::handle(), Euro::class);
    }
}

That's it. Don't forget to register your service provider if you created a dedicated one.

Gnu answered 16/2, 2022 at 12:40 Comment(2)
I'm currently no longer working on the project for which I desired this, so I can't trial it. I'll assume it works though :)Lumbricalis
Since your code indicates PHP 8, it is Laravel 9 compatible, right?Lobworm
D
5

This solution work for me.

php artisan make:rule NoLink

App\Rules\NoLink.php

<?php
namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Validation\Validator;

class NoLink implements Rule
{
    // ...

    public function message()
    {
        return __('validation.' . self::handle());
    }

    public static function handle(): string
    {
        return 'no_link';
    }

    public function validate(string $attribute, $value, $params, Validator $validator): bool
    {
        $handle = $this->handle();

        $validator->setCustomMessages([
            $handle => $this->message(),
        ]);

        return $this->passes($attribute, $value);
    }
}

App\Providers\AppServiceProvider.php

    public function boot()
    {
        // ...
        Validator::extend(NoLink::handle(), NoLink::class);
    }
Dismay answered 28/3, 2022 at 18:44 Comment(1)
Illuminate\Contracts\Validation\Rule is deprecated nowadays. I've added an updated version as answer: https://mcmap.net/q/1270060/-give-name-to-custom-validation-rule-laravel-8Evangelize
E
2

Nowadays (at least in Laravel 10) here is how it worked for me..

Since Illuminate\Contracts\Validation\Rule is depricated we now need to implement use Illuminate\Contracts\Validation\ValidationRule.

This needs a validate function but we can simply use the passes function to do the actual check

<?php

declare(strict_types=1);

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Validator;

class IsUniqueNickname implements ValidationRule
{
    public static function register(): void
    {
        Validator::extend('is_unique_nickname', self::class . '@passes', (new self)->message());
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message(): string
    {
        return 'Your custom message here';
    }

    /**
     * Check if the rule passes based on the given arguments.
     * @param string $attribute
     * @param mixed $value
     * @return bool
     */
    public function passes(string $attribute, mixed $value): bool
    {
        if ($value == 'Some Unique Nickname') { // Do some useful checks here..
            return true;
        }

        return false;
    }

    /**
     * Run the validation rule.
     * @param string $attribute
     * @param mixed $value
     * @param Closure(string): PotentiallyTranslatedString $fail
     * @return void
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!$this->passes($attribute, $value)) {
            $fail($this->message());
        }
    }
}

You can "register" the custom Rule in the AppServiceProvider like this:

<?php

namespace App\Providers;

use App\Rules\IsUniqueNickname;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        IsUniqueNickname::register();
    }
}

What it comes down to is this

The rule needs the passes method to use it like this 'nickname' => ['required', 'is_unique_nickname'],

The rule needs the validate method to use it like this 'nickname' => ['required', new IsUniqueNickname()],

Advantage of the second way is that you could give some arguments to the class to begin with and that the message can be dynamic based on the state of the rule.

Evangelize answered 16/7 at 10:21 Comment(0)
S
0

You can use the extend function on the validator. Probably something like:

Validator::extend('euro', new Euro());

This code should be in your AppServiceProvider.

Soldo answered 25/8, 2021 at 11:34 Comment(1)
This doesn't work. I put dd($value) in the Euro's passes() method, and it never triggers...Lumbricalis

© 2022 - 2024 — McMap. All rights reserved.