Mark
Mark

Reputation: 1376

How can I chain multiple form validation requests together?

In a Laravel project I'm working on, I'm wanting to create an API. In that API there will be certain JSON keys that will be required in every request. E.g. a token, or other fields that are ALWAYS required. I am familiar with Laravel's form request feature which allows you to easily create a class with a rules method containing an array of validation logic. However, I am wanting to know if there is a way where I can make one Request class that handles the "always required" fields and then bolt on another request class containing specific field validation for that endpoint.

E.g.

// MasterRequest.php
public function rules() {
    return [
        'api_key' => 'required|exists:users,api_key',
    ];
}

// ProductRequest.php
public function rules() {
    return [
        'product_id' => 'required|integer',
    ];
}

And then some way always call MasterRequest validation on EVERY api route, and then specify the type of request validation for each route's unique needs?

Is this doable, or even the correct approach?

Upvotes: 3

Views: 2386

Answers (2)

Yevgeniy Afanasyev
Yevgeniy Afanasyev

Reputation: 41320

You can manually execute Request classes one by one:

public function store()
{
    try{
        app(MasterRequest::class);
    } finally {
        app(ProductRequest::class);
    }
    /*... */
}

Upvotes: 0

Kyslik
Kyslik

Reputation: 8385

This is fairly easy to set up, use OOP properties of PHP.

The easiest way (and obvious one):

Make yourself "master class for always required fields" you can also declare it as abstract class.

File AlwaysRequired.php

abstract class AlwaysRequired extends FormRequest 
{
    public function rules() {
        return [
            'api_key' => 'required|exists:users,api_key',
        ];
    }
}

and ProductRequest.php

class ProductRequest extends AlwaysRequired 
{
    public function rules() {
        return array_merge(parent::rules(), 
                          ['product_id' => 'required|integer']);
    }
}

Array merge on php.net

The property way:

Make yourself master class in which you will declare property with "always required" rules and then just array_merge(array,...) it in child class (just like example above).

The "hardest" and most confusing way, but fully automatic:

You can leverage magic functions and method/property visibility of PHP language.

Again make yourself a master class in which you will have a protected property with rules array and implementation for __call() magic method.

Note: You can test code below in interactive shell of PHP $php -a and copy-paste the code.

abstract class A { // Master class
    protected $rules = ['abc' => 'required'];

    function __call($name, $arg) {
        if(method_exists($this, 'rules')){
            return array_merge($this->rules, $this->rules());
        } else {
            //or handle any other method here...
            die(var_dump($name, $arg));
        }
    }
}

class B extends A { //Generic class just like ProductRequest...
    protected function rules() { // function must be declared as protected! So its invisible for outside world.
        return ['def' => 'required'];
    }
}

$b = new B();
var_dump($b->rules());

How it works?

Laravel behind the scenes tries to run rules() method on the request class you specify (in your case ProductRequest), declaring it as protected means that it can not be called except from itself or by another child, which means that __call() method is called instead which is declared in abstract parent class. __call() method simply identifies if caller wanted to call non-existent (because of protected visibility is set) method rules() if that is so it merges the child's rules() result with $rules and returns it.


Checking for correct API key should be handled in Middleware.

Upvotes: 3

Related Questions