Alexandre Reis Ribeiro Alexandre Reis Ribeiro - 7 months ago 36
PHP Question

How to make Request type dynamic (validation) on Laravel 5.1

I'm trying make a crud controller base, from where i extend it and set the model base then i have some basic crud methods. I got all working dynamically. But I can't make a dynamic request type, for validate it, i have the ChannelRequest, its working ok as follows but i want it dynamic:

this is my CrudController Class (that I'll extend and set an model):

public function store(ChannelRequest $request)
{
$this->save($request); // this method get the model instantiated in parent class and save the inputs

return redirect('admin/' . $this->plural);
}


in this example above i hardcoded the request type on dependency injection, then it validate, but i want to dynamically change the request type, like this:

// i know it not being work
public function store($this->model .'Request' $request)
{
$this->save($request);

return redirect('admin/' . $this->plural);
}


i tried this:

public function store()
{
$request = new ChannelRequest();
$request->validate(); //hopping it runs like when dependency injection

$this->save($request);

return redirect('admin/' . $this->plural);
}


this throws me to an error:

FatalErrorException in FormRequest.php line 75:
Call to a member function make() on null
in FormRequest.php line 75
at FatalErrorException->__construct() in HandleExceptions.php line 133
at HandleExceptions->fatalExceptionFromError() in HandleExceptions.php line 118
at HandleExceptions->handleShutdown() in HandleExceptions.php line 0
at FormRequest->getValidatorInstance() in ValidatesWhenResolvedTrait.php line 20
at FormRequest->validate() in CrudController.php line 67

Answer

First of all I want to stress that having separate controllers for each resource (model) is a good practice and prevents separate concerns from getting too intermixed. Using a dynamic Request class defeats the purpose of explicitly defining a request class in the first place.

However, in the interest of answering the question the best I can I will give you an idea of how to solve this. This code is untested, but the concept should be sound.

What I've done here is extended the standard Request class with a SmartRequest class and overridden the __construct to allow me to run a pre-loader for the proper request class for the given request type.

This will allow you to define the separate request classes, and then load them into a SmartRequest::$subRequest property based on a resourceType request parameter (this can be part of the POST, GET or URL params if you want to modify the code some for the last one).

Code: App\Http\Requests\SmartRequest

<?php 

use App\Http\Requests\Request;

class SmartRequest extends Request {

    /**
     * Holds sub request class
     * @var Request
     */
    protected $subRequest;

    /**
     * Default constructor
     * @param array             $query      
     * @param array             $request    
     * @param array             $attributes 
     * @param array             $cookies    
     * @param array             $files      
     * @param array             $server     
     * @param string|resource   $content    
     */
    public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
    {
        // make sure standard constructor fires
        parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content);

        // instantiate the sub request object, we must also pass through all the data for the base
        // request since the sub class requires this data.
        $this->loadSubRequest($query, $request, $attributes, $cookies, $files, $server, $content);
    }

    /**
     * Default constructor
     * @param array             $query      
     * @param array             $request    
     * @param array             $attributes 
     * @param array             $cookies    
     * @param array             $files      
     * @param array             $server     
     * @param string|resource   $content    
     */
    public function loadSubRequest($query, $request, $attributes, $cookies, $files, $server, $content)
    {   
        // get resource type off the request data to generate the class string
        $class = $this->getRequestClassName();

        $this->subRequest = new $class($query, $request, $attributes, $cookies, $files, $server, $content);
    }

    /**
     * Get the sub class name with namespace
     * @return string
     */
    public function getRequestClass()
    {
        return '\<path>\<to\<namespace>\\' . studly_case($this->resourceType) . 'Request';
    }

    /**
     * Returns rules based on subclass, otherwise returns default rules
     * @return array
     */
    public function rules()
    {
        // return the default rules if we have no sub class
        if (empty($this->subRequest)) return [];

        // return the rules given by the sub class
        return $this->subRequest()->rules();
    }
}

Again, this is not real code (as in I have not tested it) but this might be a way to accomplish your request. This also relies on there being some identifier sent on the request (in this case, a requestType parameter), since you won't know anything about request other then where it was sent and with what parameters.

Still, I think this is quite against the intention of this functionality. It is far better to have explicit requests and have them used explicitly in the methods that require them. Why? Self documenting code. People will know what you're using where just by reading things such as ChannelRequest $request in the action. Where something like the above (SmartRequest) will result in some kind of magic happening that any other developer will not understand until tearing open the SmartRequest class.

Let me know if this was confusing, or if you have any other questions about why I think this approach is a step in the wrong direction.

Comments