How to add a custom validation Rule in Laravel 10

Jan 18, 2024

Despite the power of Laravel's robust pre-defined validation infrastructure. In certain scenarios, the necessity arises to craft a custom validation rule to precisely align with the unique validation needs of your Laravel application.

Embark on a comprehensive step-by-step guide, delving into the creation and enforcement of a custom Laravel validation rule for your specific needs.

Custom Validation can be applied in scenarios like affiliate marketing webstites to validate user-provided affiliate codes during registration, ensuring their authenticity by cross-referencing with the database for a genuine and secure user experience.

Experience a practical example within the realm of affiliate marketing. Validate user-provided affiliate codes by cross-referencing them with our database. In case of failed validation, a clear error message is returned, while successful validation seamlessly progresses the user registration process.

Here is the step by step guide on how to add a custom validation rule in laravel 10.

Step 1: Install a fresh laravel application.

Initiate the process by installing a new Laravel application through Composer. Navigate to your project folder in the terminal and execute the following command to kickstart the installation.

        
          composer create-project laravel/laravel affiliate-app
        
      

Next, install Jetstream and Livewire into your Laravel application to seamlessly incorporate authentication scaffolding.

Navigate into the 'affiliate-app' directory in the terminal and execute the provided commands to proceed with the setup

        
          composer require laravel/jetstream
        
      

        
          php artisan jetstream:install livewire
        
      

Open your project in the preferred code editor of your choice.

Step 2: Update The migrations

Proceed to update the users migration in order to add the affiliate_code and the affiliate fields to the users table.

Navigate to the 'database/migrations' directory and make necessary updates to the 'create_users_table' migration.

        
          

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->string('affiliate_code')->unique();
            $table->string('affiliate')->nullable();
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->string('profile_photo_path', 2048)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};

        
      

Update the database credentials in the .env file and execute the migrations using the following command:

        
          php artisan migrate
        
      

Step 3: Generate affiliate code

Generate the affiliate code and seamlessly integrate it into the user data during the registration process.

Begin by updating the fillable array in the User model. Head to 'app/models/User.php' and update the fillable array to include the affiliate_code and affiliate fields.

        

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'affiliate_code',
        'affiliate'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];
}
        
      

Proceed to 'app/Actions/Fortify/CreateNewUser.php' to incorporate the logic for generating affiliate codes.

        

namespace App\Actions\Fortify;

use App\Models\User;
use App\Rules\AffiliateCode;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;

class CreateNewUser implements CreatesNewUsers
{
    use PasswordValidationRules;

    //generate affiliate code for new user
    public function generateAffiliateCode(){
        $code = Str::random('8');
        $user_with_code = User::where('affiliate_code', '=', $code)->count();
        if ($user_with_code == 0){
            return $code;
        }
        return $this->generateAffiliateCode();
    }

    public function create(array $input): User
    {
        Validator::make($input, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => $this->passwordRules(),
            'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
        ])->validate();

        return User::create([
            'name' => $input['name'],
            'email' => $input['email'],
            'password' => Hash::make($input['password']),
            'affiliate_code' => $this->generateAffiliateCode()
        ]);
    }
}
        
      

Upon registration, a unique affiliate code will be automatically generated for each new user account.

Step 4: Create the Validation rule.

Utilize the 'php artisan' command to craft a custom validation rule seamlessly.

        
          php artisan make:rule AffiliateCode.
        
      

Navigate to 'app/Rules/AffiliateCode.php' to implement the validation logic for the affiliate code.

        

namespace App\Rules;

use App\Models\User;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class AffiliateCode implements ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $user = User::where('affiliate_code', '=', $value)->first();
        if(!$user){
            $fail('Invalid affiliate code');
        }
    }
}

        
      

Step 5: Apply the validation rule in action.

With the validation rule in place, let's integrate it by first updating the register view. Include the affiliate code input field, allowing users to provide the affiliate code of the referrer who directed them to the website.

Navigate to 'resources/views/auth/register.blade.php' and enhance the file by adding the affiliate_code input field as illustrated below.

      
        <x-guest-layout>
    <x-authentication-card>
        <x-slot name="logo">
            <x-authentication-card-logo />
        </x-slot>

        <x-validation-errors class="mb-4" />

        <form method="POST" action="{{ route('register') }}">
            @csrf

            <div>
                <x-label for="name" value="{{ __('Name') }}" />
                <x-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
            </div>

            <div class="mt-4">
                <x-label for="email" value="{{ __('Email') }}" />
                <x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
            </div>

            <div>
                <x-label for="affiliate-code" value="{{ __('Affiliate Code') }}" />
                <x-input id="affiliate-code" class="block mt-1 w-full" type="text" name="affiliate_code" :value="old('affiliate_code')" />
            </div>

            <div class="mt-4">
                <x-label for="password" value="{{ __('Password') }}" />
                <x-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
            </div>

            <div class="mt-4">
                <x-label for="password_confirmation" value="{{ __('Confirm Password') }}" />
                <x-input id="password_confirmation" class="block mt-1 w-full" type="password" name="password_confirmation" required autocomplete="new-password" />
            </div>

            @if (Laravel\Jetstream\Jetstream::hasTermsAndPrivacyPolicyFeature())
                <div class="mt-4">
                    <x-label for="terms">
                        <div class="flex items-center">
                            <x-checkbox name="terms" id="terms" required />

                            <div class="ms-2">
                                {!! __('I agree to the :terms_of_service and :privacy_policy', [
                                        'terms_of_service' => ''.__('Terms of Service').'',
                                        'privacy_policy' => ''.__('Privacy Policy').'',
                                ]) !!}
                            </div>
                        </div>
                    </x-label>
                </div>
            @endif

            <div class="flex items-center justify-end mt-4">
                <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
                    {{ __('Already registered?') }}
                </a>

                <x-button class="ms-4">
                    {{ __('Register') }}
                </x-button>
            </div>
        </form>
    </x-authentication-card>
</x-guest-layout>
      
      

Return to 'app/Actions/Fortify/CreateNewUser.php' to implement the validation rule during the user registration process.

          

namespace App\Actions\Fortify;

use App\Models\User;
use App\Rules\AffiliateCode;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;

class CreateNewUser implements CreatesNewUsers
{
    use PasswordValidationRules;

    //generate affiliate code for new user
    public function generateAffiliateCode(){
        $code = Str::random('8');
        $user_with_code = User::where('affiliate_code', '=', $code)->count();
        if ($user_with_code == 0){
            return $code;
        }
        return $this->generateAffiliateCode();
    }

    public function create(array $input): User
    {
        Validator::make($input, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => $this->passwordRules(),
            'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
            'affiliate_code' => [new AffiliateCode, 'nullable']
        ])->validate();

        return User::create([
            'name' => $input['name'],
            'email' => $input['email'],
            'password' => Hash::make($input['password']),
            'affiliate_code' => $this->generateAffiliateCode(),
            'affiliate' => $input['affiliate_code']
        ]);
    }
}

          
        

Proceed to test the validation in your web browser.

"Initiate the server by using the following command:


        php artisan serve
      

And access the project in your web browser.

To begin, register an account without entering an affiliate code, as there are currently no users in the database. This will generate an account with an affiliate code for testing purposes.

validation 1

With an account featuring an affiliate code in the database, proceed to test the validation. Start by creating a new user account with an intentionally invalid affiliate code.

validation 2

Subsequently, input a valid affiliate code already present in the database. Upon successful registration, you will be redirected to the dashboard.

validation 3

Conclusion.

This concludes the step-by-step guide on incorporating custom validation into your Laravel application, illustrated through a straightforward example involving affiliate link validation.

Thanks and best wishes.