2

Is it possible to transform Illuminate\Http\Request to custom validation request you made with php artisan make:request MyRequest?

I would like validation to take place in a method down the road so that I have:

protected function register(Request $request)
{
   ...
   $this->userRepository->signup($request)
   ...
}

User repository:

public function signup(MyRequest $request)
{
    ...
}

Is this possible? I am getting an error now because one class is expected. Only thing that comes to mind is to make an interface, but I'm not sure if that could function.

Error I get

Type error: Argument 1 passed to UserRepository::signup() must be an instance of App\Http\Requests\MyRequest, instance of Illuminate\Http\Request given

Norgul
  • 4,613
  • 13
  • 61
  • 144

4 Answers4

3

Well, you can convert any request to any other request, as long as it extends from Illuminate\Http\Request.

There are basically two methods Laravel uses to convert one request to another. The problem here is that it will never get a validation object or trigger the validation automatically, as part of the injection when MyRequest was passed as an argument. It might also miss a message bag and the error handler, but nothing you can fix by initializing the request just like Laravel does when injecting it.

So you still have to trigger all the sequences the FormRequest (if it extends FromRequest rather than Request) normally does when booting the trait, but still, it's entirely possible and with some little extra work, you could convert any request to any other request.

For example; I'm using this setup to call just one route profile/{section}/save for saving my profile settings. Depending on $section's value, I convert the given $Request to any of my custom form requests for that particular $section.


use App\Http\Requests\MyRequest;
use Illuminate\Http\Request;

...

public function someControllerMethod(Request $Request) {
   $MyRequest = MyRequest::createFrom($Request);
   // .. or
   $MyRequest = MyRequest::createFromBase($Request);
}

...

So to get people started with using a FormRequest as an example, it basically comes to this.

Instead of extending all your custom requests from the default Illuminate\Foundation\Http\FormRequest, use a base class which extends from FormRequest and add a custom method to transform and boot the request as if it were passed as an argument.

namespace App\Http\Requests;

use Illuminate\Routing\Redirector;
use Illuminate\Foundation\Http\FormRequest;

class BaseFormRequest extends FormRequest {
   public function convertRequest(string $request_class) : BaseFormRequest {
      $Request = $request_class::createFrom($this);

      $app = app();
      $Request
        ->setContainer($app)
        ->setRedirector($app->make(Redirector::class));

      $Request->prepareForValidation();
      $Request->getValidatorInstance();

      return $Request;
   }

    public function authorize() {
        return true;
    }

    public function rules() {
        return [];
    }
}

Let all your custom FormRequest extend your BaseFormRequest

namespace App\Http\Requests;

class MyRequest extends BaseFormRequest {
  ...
}

Now anywhere you want to convert a request, use the base class in your controller method and convert it using convertRequest with the custom request class you wish to convert.

public function someControllerMethod(BaseFormRequest $Request) {
  $MyRequest = $Request->convertRequest(MyRequest::class);
}
dbf
  • 3,278
  • 1
  • 24
  • 34
  • 1
    do you have any idea, to make this still work, but with get the validation object or trigger the validation automatically? – Manyang Jul 31 '22 at 15:26
1

like @dbf answer but with automatic validation

use App\Http\Requests\MyRequest;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
public function someControllerMethod(Request $Request) {

   //this make your request class do validation
   try{
        app(MyRequest::class);
    } catch (ValidationException $ex){
        throw $ex;
    }

   //if no error you can continue to convert the request
   $MyRequest = MyRequest::createFrom($Request);
   // .. or
   $MyRequest = MyRequest::createFromBase($Request);
}
Manyang
  • 180
  • 12
0

Yes, there is no problem with that, you should create:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class MyRequest extends FormRequest
{
   public function rules() {
       // here you put rules
   }
}

and in your controller:

public function signup(\App\Http\Requests\MyRequest $request)
{
    ...
}

Be aware you should also adjust authorize method in your request class (to return true or false depending on user access)

EDIT

After update - you should type hint your custom class in controller and in repository - it's up to you - I often use generic Illuminate\Http\Request, so you should do:

in controller:

public function controllerMethod(\App\Http\Requests\MyRequest $request)

in repository:

public function signup(\App\Http\Requests\MyRequest $request)

or

public function signup(\Illuminate\Http\Request $request)

So to sum up you should use Form request classes in controller - this is the place where validation will be made and later you can use either same class or generic \Illuminate\Http\Request - I personally often use in repositories or services just \Illuminate\Http\Request because they usually don't care about other things put into MyRequest class - they just want to get data from request class and that's it.

Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
  • I don't think you understood what I'm asking for. I am getting error when I forward Illuminate Request to the method which takes MyRequest as a parameter – Norgul Dec 18 '17 at 09:25
  • This is because you pass invalid object there. In controller you should have somewhere `public function controllerMethod(\App\Http\Requests\MyRequest $request)` and then in controller method you can use: `$this->userRepository->signup($request)` without any problem. Now you use in controller `\Illuminate\Http\Request` so it won't work as I explained in my answer – Marcin Nabiałek Dec 18 '17 at 09:27
  • I know what I am passing :) I was asking for a possible workaround. I am aware that those are 2 different classes :) – Norgul Dec 18 '17 at 09:29
  • There is no workaround for this. You cannot pass let's say object of `Apple` class to method which requires `Onion` class - it won't work. If you don't care about validation you can create just `MyRequest` class with no rules in it but you need to pass object of `MyRequest` class to `signup` method – Marcin Nabiałek Dec 18 '17 at 09:31
  • I was thinking it can be done since MyRequest ultimately extends from Request – Norgul Dec 18 '17 at 09:34
  • If you wanted Request class in signup method you can pass their Request class or any other class extending from Request class (so it can be MyRequest for example), but if you want to get MyRequest, you have to pass either MyRequest object or object of class extending from MyRequest – Marcin Nabiałek Dec 18 '17 at 09:35
0

I didn't find it was possible to do what I wanted even with my custom class extending the Request because naturally one method expects an instance of one class while getting another one.

Maybe it would be possible to extract an interface out and wrap and bind it but that would be in my opinion a quickfix.

My opinion is that concept I had was wrong from the start, and it was more of an architecture problem so I transformed my app to a different approach and manage to avoid such issues in the first place.

Norgul
  • 4,613
  • 13
  • 61
  • 144