Steve McDougall Avatar
Published on

Introducing - Laravel Transporter

Authors
Introducing - Laravel Transporter

Sending API requests in any PHP framework has always been a little bit of a manual process, yes you can create an SDK or wrapper - but you are still having to do the same thing.

You pull in the HTTP client, or facade, you configure it in a proceedural way entering the URI you want to send a request to, then tag on the optional extras such as authentication, payload, any additional headers. It is quite a manual process.

A lot of the time you have a specific request you want to send, yes you may slightly adjust things as you go by passing in an identifier etc etc - but in general they remain relatively constant.

This has frustrated me for a while, we have to great lengths to make a lot of our code Object Oriented. Yet, we hadn't tried to do this with API requests. I have been sitting on this question for quite a while, pondering on the possible solutions - how it might look, and how it might be used.

The result of this has turned into my latest Laravel package: Laravel Transporter which I describe as:

Transporter is a futuristic way to send API requests in PHP. This is an OOP approach to handle API requests.

Quite a bold statement, so let me dig in.

To get started all you need to do is install it, theres no configuration required nothing extra to add to your project - it will only be used when you want to use it.

Then, let's take an example API request:

GET https://jsonplaceholder.typicode.com/todos?completed=true HTTP/1.1

All we are doing here is filtering a list of todos that are completed - nothing overly difficult.

Firstly let us have a look at how we would do this usually (but let's pretend this API required us to be authenticated using an API token we have previously generated).

1Http::withToken(config('jsonplaceholder.api.token'))
2->get("https://jsonplaceholder.typicode.com/todos", [
3 'completed' => 'true',
4]);

Not terrible right? I mean, it works and does what you might expect. But, this is very proceedural. What happens if the URL changes? What happens if the query parameters change? We have to hunt through our code base, and update these everywhere. You know, the thing we are trying to avoid more and more in everything we build.

Let's add a little magic to these requests and see how we would send this exact same request using Laravel Transporter:

1TodoRequest::build()->send();

That's it. The entire thing condensed into a class. Let's look how we got there.

First thing we do, create our request:

1php artisan make:api-request TodoRequest

This gives us app/Transporter/TodoRequest.php, inside there we have:

1<?php
2 
3declare(strict_types=1);
4 
5namespace App\Transporter;
6 
7use Illuminate\Http\Client\PendingRequest;
8use JustSteveKing\Transporter\Request;
9 
10class TodoRequest extends Request
11{
12 protected string $method = 'GET';
13 protected string $baseUrl = 'https://jsonplaceholder.typicode.com';
14 protected string $path = '/todos';
15 
16 protected array $data = [
17 'completed' => true,
18 ];
19 
20 protected function withRequest(PendingRequest $request): void
21 {
22 $request->withToken(config('jsonplaceholder.api.token'));
23 }
24}

Going back to the original problem, if options change - we have to hunt everywhere in our application for where we may have used it. That problem has been solved by moving this to a class based request. Also, as the request itself is just a fancy wrapper around Laravels inbuilt PendingRequest - we can call all the same methods before and after sending. Meaning there is no new API to learn, so it is relatively straight forward. You can override options at runtime too - want to show not completed todos?

1TodoRequest::build()->withData([
2 'completed' => false,
3])->send();

This may not be a ground breaking package that is going to change the world, however what it is going to do is make you start asking the question: Could I send API requests in a more structured and organised way?

Imagine a scenario, where you needed to work with a 3rd party API. You could quite quickly generate a series of requests and it will be done! Let's take the example of Laravel Forge, which if you remember I wrote about before when I released PHP-SDK here is that article.

First we can generate a base API request:

1php artisan make:api-request Forge\\ForgeRequest

Then make these changes:

1<?php
2 
3declare(strict_types=1);
4 
5namespace App\Transporter\Forge;
6 
7use Illuminate\Http\Client\PendingRequest;
8use JustSteveKing\Transporter\Request;
9 
10class ForgeRequest extends Request
11{
12 protected string $baseUrl = 'https://forge.laravel.com/api/v1';
13 
14 protected function withRequest(PendingRequest $request): void
15 {
16 $request->withToken(config('services.forge.token'));
17 }
18}

Next let's take the example of getting all servers. Generate a new request for this request:

1php artisan make:api-request Forge\\Servers\\ListServers

Then make the following changes:

1<?php
2 
3declare(strict_types=1);
4 
5namespace App\Transporter\Forge\Servers;
6 
7use App\Transporter\Forge\ForgeRequest;
8use Illuminate\Http\Client\PendingRequest;
9 
10class ListServers extends ForgeRequest
11{
12 protected string $method = 'GET';
13 
14 protected string $path = '/servers';
15}

All we have done here is used inheritance to extend the ForgeRequest which contains our initial state for every request we need - and allows us to build upon that where we want to.

Thanks for reading, I welcome any feedback that may assist in pushing this package forward and making it easier to use! Feel free to start a discussion or open an issue on the GitHub Repository or even drop me a tweet/DM on twitter.