enkay's avatar
Level 5

Calling own API from Controller in Laravel and validation with FormRequest

I want to call my own API routes from controllers on a Laravel 5.2 app.

It works for the most part using the following:

$request = Request::create('api/v1/posts', 'POST')
$response = Route::dispatch($request)

Now on the API controller, I am using FormRequest to validate the request.

When validation fails, the default behavior of FormRequest is to redirect back with errors unless it's an ajax request, in which case it returns a JSON response.

Currently the $request is not getting interpreted as ajax and the FormRequest returns a RedirectResponse instead of a JsonResponse.

Is there away to specify that the $request I created should be interpreted as ajax or that it only accepts JSON responses? I haven't found a way to specify headers on the request.

I know I can override the response method on the FormRequest to always return JSON, but I would rather fix it on the request level.

Thanks.

Update: It seems adding the following works to change the request headers, however FormRequest is still not accepting it as ajax or wantsJson.

$request->headers->set('Accept', 'application/json');

The request received by the FormRequest has different Accept headers (and other headers too) than the ones I set which is why wantsJson is returning false. Where are the headers getting modified?

Update 2: Swapping the FormRequest on the API controller store method to Illuminate\Http\Request, we get the right Accept header on the request. So it seems like the issues comes from FormRequest.

On the API controller, this returns false (using FormRequest):

public function store(StorePostRequest $request)
{
    dd($request->wantsJson());
}

This returns true (using Illuminate\Http\Request):

public function store(Request $request)
{
    dd($request->wantsJson());
}
0 likes
12 replies
nfauchelle's avatar

@enkay

How come you want to call a controller from a controller?

That sounds like you have some logic which needs to be extracted out.

A controller, in a sense is a entry point to your code. Once in, you shouldn't need to "re enter" the code...

enkay's avatar
Level 5

No offence, but I'd like to understand why this is not working as expected.

If you have a better way to call my own API from my web controllers, please do share but as it stands your answer is quite generic and doesn't help me understand why the code I posted doesn't work, or how I should architecture my code in a better way.

kfirba's avatar

@enkay well yea, there is a better way. Just execute the same code your endpoint is executing. I obviously don't mean copy-paste the code from one controller method to another but to extract the action that's done in the API route to some class and then you can re-use that action.

For example, if it's creating a post, you can extract that method to the post model and add a custom method there. Then you call this method from both your API endpoint and the "regular" website route.

If you ever find yourself needing to call another controller method from your controller method, it probably means you miss some kind of abstraction

enkay's avatar
Level 5

@kfirba

I'm currently using a helper api() function which abstracts most of this.

Web Controller:

public function store(Request $request)
{
    $post = api('api.posts.store', 'POST');
    // do something else    
}

Api Controller:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->all());

    return response()->json($post);
}

I've thought about the approach you're suggesting, but this seems pretty clean to me.

martinbean's avatar

@enkay I’m with @nfauchelle and @kfirba: if you have an application then just use models and hit the database rather than calling your own API. APIs incur a HTTP request which is going to be slower than executing a database statement.

By all means, have an API for external applications (like native mobile apps that you don’t want to connect to your database directly), but this just seems like an ill-conceived approach at “API-driven development”, or using your API because you have an API.

3 likes
pmall's avatar

If you want to call an api, use something like Guzzle. Do not directly call route dispatch or whatever.

1 like
nfauchelle's avatar

@enkay

There are a few things you'll have to think about and handle, for example the other route you call might not have the same middleware / state as what you expect.

I feel as though using a command bus might be more suitable.

It used to be in 5.0 and 5.1, but was extracted out in 5.2 https://laravelcollective.com/docs/5.2/bus

https://laracasts.com/series/commands-and-domain-events/episodes/2 https://laracasts.com/series/commands-and-domain-events

enkay's avatar
Level 5

@martinbean There is no extra http request since I'm calling the route internally.

@pmall That would actually incur an extra http request which would make it slower.

@nfauchelle That seems like a lot of overhead for something that's currently all self contained in a small helper function.

Also no one actually tried to answer my original question, which is why the proper Accept header gets lost when using a FormRequest but not a basic Illuminate Request.

pmall's avatar

That would actually incur an extra http request which would make it slower.

So forget querying your own api and use the database.

1 like
spekkionu's avatar

I don't see why you don't just abstract the post creation to its own class/function and call it from both the api and web controllers.

digitlimit's avatar

@enkay I don't know if have resolved this issue.

Consuming your own API from same Laravel application is a good idea especially if you don't wish to repeat yourself (DRY)

To ensure JSON is always returned on validation failure you could simply modify Form Request.

In L5.2 you can find form request in App\Http\Requests

Simply change the response method like so:

 public  function response(array $errors){
     return response()->formError($errors, $this->validator);
}
fahmi's avatar

If you want to call an api, use something like Guzzle. Do not directly call route dispatch or whatever.

Agree with this. Even Taylor Otwell said the same.

Please or to participate in this conversation.