WackyStudio / build-an-api-with-laravel

Official Build an API with Laravel repository
122 stars 56 forks source link

Question regarding Unique Email Users #21

Closed falconeri closed 4 years ago

falconeri commented 4 years ago

Inside jsonapi config, i found users validation rules email but without unique constraint, both create and update. I know how to add unique for create, but how to add unique constraint for update? how do i add user id into unique constraint? thanks

ThomasNoergaard commented 4 years ago

If you take a look at the JSONAPIRequest class and the mergeConfigRules method you'll see that the rules given in the jsonapi config file is being called. This means that the rules inside the jsonapi config file follows the exact same validation rules as you would write inside a FormRequest class or a Validator in Laravel.

To add the unique rule you can do it like this:

 'validationRules'=> [
    'create' => [
        'data.attributes.name' => 'required|string',
        'data.attributes.email' => 'required|email|unique:users,email',
        'data.attributes.password' => 'required|string',
    ],
    'update' => [
        'data.attributes.name' => 'sometimes|required|string',
        'data.attributes.email' => 'sometimes|required|email|unique:users,email',
        'data.attributes.password' => 'sometimes|required|string',
    ]
],

If you want to add a unique rule to the id attribute you can do it like this: 'data.id' => 'required|string|unique:users,id' With the unique rule you first give the tabel in the database and then the column the unique rule should look at while determining if a given value is unique.

I'm not sure why you need the unique rule on ID's, can you elaborate on this? We highly recommend that you leverage auto increments in the database or generate UUIDs while creating a model instead of opening up the ability for your consumers to create their own ID's.

falconeri commented 4 years ago

sorry. i think my question not clear enough. i mean to add id (user id) to unique checking when update user, so if email is exist but id == current id then it will skip the unique constraint. if id != current id then it will checking duplicate emails then show errors if found.

Usually i put $this->route('user') or $this->route('id') in form request. but this validation rules are inside config files, so i cannot add $this inside there. I hope you can understand my question correctly. thanks

 'validationRules'=> [
    'create' => [
        'data.attributes.name' => 'required|string',
        'data.attributes.email' => 'required|email|unique:users,email',
        'data.attributes.password' => 'required|string',
    ],
    'update' => [
        'data.attributes.name' => 'sometimes|required|string',
        'data.attributes.email' => 'sometimes|required|email|unique:users,email'.$this->route('id),
        'data.attributes.password' => 'sometimes|required|string',
    ]
],
ThomasNoergaard commented 4 years ago

Yes, I think I understand what you mean now, thank you.

This is one of the caveats of using a configuration like this, since we will loose the ability to access the request and/or authentication data and therefore can't give an ID of the current authenticated user.

In these cases I create a custom validation rule, since these gives us the ability to access the request and authentication again and it can be reused if necessary.

If you go into your AppServiceProvider you can define a new validation rule like this, that can handle this for you:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;

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

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::extend('uniqueIfNotAuthenticated', function ($attribute, $value, $parameters, $validator) {
            return !Validator::make([
                'data' => $value
            ], [
                'data' => "unique:{$parameters[0]},{$parameters[1]},".Auth::id(),
            ])->fails();
        });
    }
}

Here we're just wrapping the unique validation rule and adding the authenticated user ID onto it, which then gives us a rule we can use inside our jsonapi config file like this:

<?php
'validationRules'=> [
    'create' => [
        'data.attributes.name' => 'required|string',
        'data.attributes.email' => 'required|email|unique:users,email',
        'data.attributes.password' => 'required|string',
    ],
    'update' => [
        'data.attributes.name' => 'sometimes|required|string',
        'data.attributes.email' => 'sometimes|required|email|uniqueIfNotAuthenticated:users,email',
        'data.attributes.password' => 'sometimes|required|string',
    ]
],

In this way we have a nice descriptive name and we can continue using our jsonapi config file.

I hope this answered your question?

falconeri commented 4 years ago

Thank you for your explanation. i also done some custom script to add user id into validation, but i think your custom validation rule way more cleaner.