Making internal requests with Laravel

Published by at 2nd September 2017 1:45 pm

Recently I've been working on a Phonegap app that needs to work offline. The nature of relational databases can often make this tricky if you're dealing with related objects and you're trying to retrofit it to something that wasn't built with this use case in mind.

Originally my plan was to push each request that would have been made to a queue in WebSQL, and then on reconnect, make every request individually. It quickly became apparent, however, that this approach had a few problems:

  • If one request failed, the remaining requests had to be stopped from executing
  • It didn't allow for storing the failed transactions in a way that made them easy to retrieve

Instead, I decided to create a single sync endpoint for the API that would accept an object containing all the requests that would be made, and then step through each one. If it failed, it would get the failed request and all subsequent ones in the object, and store them in the database. That way, even if the data didn't sync correctly, it wasn't lost, and if necessary it could be resolved manually.

Since the necessary API endpoints already existed, and were thoroughly tested, it was not a good idea to start duplicating that functionality. Instead, I implemented the functionality to carry out internal requests, and I thought I'd share how you can do this.

For any service you may build for your Laravel applications, it's a good idea to create an interface for it first:

1<?php
2
3namespace App\Contracts;
4
5interface MakesInternalRequests
6{
7 /**
8 * Make an internal request
9 *
10 * @param string $action The HTTP verb to use.
11 * @param string $resource The API resource to look up.
12 * @param array $data The request body.
13 * @return \Illuminate\Http\Response
14 */
15 public function request(string $action, string $resource, array $data = []);
16}

That way you can resolve the service using dependency injection, making it trivial to replace it with a mock when testing.

Now, actually making an internal request is pretty easy. You get the app instance (you can do so by resolving it using dependency injection as I do below, or call the app() helper). Then you put together the request you want to make and pass it as an argument to the app's handle() method:

1<?php
2
3namespace App\Services;
4
5use Illuminate\Http\Request;
6use App\Contracts\MakesInternalRequests;
7use Illuminate\Foundation\Application;
8use App\Exceptions\FailedInternalRequestException;
9
10/**
11 * Internal request service
12 */
13class InternalRequest implements MakesInternalRequests
14{
15 /**
16 * The app instance
17 *
18 * @var $app
19 */
20 protected $app;
21
22 /**
23 * Constructor
24 *
25 * @param Application $app The app instance.
26 * @return void
27 */
28 public function __construct(Application $app)
29 {
30 $this->app = $app;
31 }
32
33 /**
34 * Make an internal request
35 *
36 * @param string $action The HTTP verb to use.
37 * @param string $resource The API resource to look up.
38 * @param array $data The request body.
39 * @throws FailedInternalRequestException Request could not be synced.
40 * @return \Illuminate\Http\Response
41 */
42 public function request(string $action, string $resource, array $data = [])
43 {
44 // Create request
45 $request = Request::create('/api/' . $resource, $action, $data, [], [], [
46 'HTTP_Accept' => 'application/json',
47 ]);
48
49 // Get response
50 $response = $this->app->handle($request);
51 if ($response->getStatusCode() >= 400) {
52 throw new FailedInternalRequestException($request, $response);
53 }
54
55 // Dispatch the request
56 return $response;
57 }
58}

Also note that I've created a custom exception, called FailedInternalRequestException. This is fired if the status code returned from the internal requests is greater than or equal to 400 (thus denoting an error):

1<?php
2
3namespace App\Exceptions;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\Response;
7
8/**
9 * Exception for when a bulk sync job fails
10 */
11class FailedInternalRequestException extends \Exception
12{
13 /**
14 * Request instance
15 *
16 * @var $request
17 */
18 protected $request;
19
20 /**
21 * Response instance
22 *
23 * @var $response
24 */
25 protected $response;
26
27 /**
28 * Constructor
29 *
30 * @param Request $request The request object.
31 * @param Response $response The response object.
32 * @return void
33 */
34 public function __construct(Request $request, Response $response)
35 {
36 parent::__construct();
37 $this->request = $request;
38 $this->response = $response;
39 }
40
41 /**
42 * Get request object
43 *
44 * @return Request
45 */
46 public function getRequest()
47 {
48 return $this->request;
49 }
50
51 /**
52 * Get response object
53 *
54 * @return Response
55 */
56 public function getResponse()
57 {
58 return $this->response;
59 }
60}

You can catch this exception in an appropriate place and handle it as you wish. Now, if you import the internal request class as $dispatcher, you can just call $dispatcher->request($action, $resource, $data), where $action is the HTTP verb, $resource is the API resource to send to, and $data is the data to send.

It's actually quite rare to have to do this. In this case, because this was a REST API and every request made to it was changing the state of the application (there were no GET requests, only POST, PUT, PATCH and DELETE), it made sense to just break down the request body and do internal requests against the existing API, since otherwise I'd have to duplicate the existing functionality. I would not recommend this approach for something like fetching data to render a page on the server side, as there are more efficient ways of accomplishing it. In all honesty I can't think of any other scenario where this would genuinely be the best option. However, it worked well for my use case and allowed me to implement this functionality quickly and simply.