Jonathan Lafleur
Jonathan Lafleur

Reputation: 493

Laravel Complex Conditional Validation

I'm trying to create a validator that require at least one of three input.

I tried this

protected function validateFundingSource (): array
{
    return request()->validate([
       'title'       => 'required',
       'description' => 'required',
       'national'       => 'nullable',
       'province'       => Rule::requiredIf(!request('national')),
       'url'            => [
           'required_without_all:phone,email',
           'active_url'
       ],
       'phone'          => [
           'required_without_all:url,email|regex:/^(\+\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}(?: *#(\d+))?\s*$/im'
       ],
       'email' => [
           'required_without_all:url,phone|email:rfc,dns'
       ],
       'categories' => 'exists:categories,id'
   ]);
}

But it was was forcing only the first field (url). So I tried with Complex Conditional Validation.

protected function validateFundingSource ()
{

    $v = Validator::make(request()->all(), [
            'title'       => 'required',
            'description' => 'required',
            'national'       => 'nullable',
            'categories'     => 'exists:categories,id',
    ]);

    $v->sometimes('province', 'required', function ($input) {
        return ($input->national === null) ;
    });

    $v->sometimes('url', 'required|active_url', function ($input) {
        return (($input->phone === null) && ($input->email === null));
    });

    $v->sometimes('phone', 'required|regex:/^(\+\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}(?: *#(\d+))?\s*$/im', function ($input) {
        return (($input->url === null) && ($input->email === null));
    });

    $v->sometimes('email', 'required|email:rfc,dns', function ($input) {
        return (($input->url === null) && ($input->phone === null));
    });

    return $v;
}

But still no luck... Now it's never required I can submit all three empty field and it's working...

Any clues to help me please ?

Thank you !

Upvotes: 3

Views: 3201

Answers (2)

miken32
miken32

Reputation: 42720

If you're looking for "at least one of" url, phone, or email then you want to use required_without. This rule means the field is required when any of the specified fields are missing; required_without_all means it's required when all of the specified fields are missing.

You are also confusing rule syntax, you must use either array or pipe-delimited string syntax, not both at once.

You may want to improve your phone number regex as well; + -. (000-111.9999 #8 is not a great phone number, but would pass your validation. I'd suggest sanitizing your value to remove everything except digits and a leading +, then using a better pattern on what's left.

And, it's just a cosmetic change but you can replace Rule::requiredIf(!request('national')), with a simple required_if rule like the others.

Changing to a form request validation, this would look like:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreFundingsource extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $phone = preg_replace("/[^0-9]/", "", $this->phone);
        if (strpos($this->phone, "+") === 0) {
            $phone = "+$phone";
        }
        $this->merge(["phone"=>$phone]);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
           'title'       => ['required'],
           'description' => ['required'],
           'national'    => ['nullable'],
           'province'    => ['required_if,national,'],
           'categories'  => ['exists:categories,id']
           'url'         => [
               'required_without:phone,email',
               'active_url'
           ],
           'phone'       => [
               'required_without:url,email',
               'regex:/^\+?1?[2-9][0-9]{5,14}$/'
           ],
           'email'       => [
               'required_without:url,phone',
               'email:rfc,dns'
           ],
       ];
    }
}

Upvotes: 2

Sok Hai
Sok Hai

Reputation: 546

You code is working fine. you just forget to check if validate pass or not. because when you use Validator::make you need to manually check it. for request()->validate laravel will do it for you. inside your validateFundingSource () function just check it pass validate or not before return like this:

private function validateFundingSource () {
        $v = Validator::make(request()->all(), [
                'title'       => 'required',
                'description' => 'required',
                'national'       => 'nullable',
                'categories'     => 'exists:categories,id',
        ]);

        $v->sometimes('province', 'required', function ($input) {
            return ($input->national === null) ;
        });

        $v->sometimes('url', 'required|active_url', function ($input) {
            return (($input->phone === null) && ($input->email === null));
        });

        $v->sometimes('phone', 'required|regex:/^(\+\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}(?: *#(\d+))?\s*$/im', function ($input) {
            return (($input->url === null) && ($input->email === null));
        });

        $v->sometimes('email', 'required|email:rfc,dns', function ($input) {
            return (($input->url === null) && ($input->phone === null));
        });

        // check if validae failed
        if($v->fails()) {
            dd('fail', $v); // do something when it failed
        }
    }

also sorry for my bad English & hope it help

Upvotes: 2

Related Questions