Every day, the Laravel framework amazes me in the way it handles very straightforward things.

One of these things is it's HTTP client.

It makes doing HTTP calls such as breeze.

Take a look at this example from Freek van der Herten:

Http::timeout(5)
    ->retry(times: 3, sleep: 1)
    ->get($url)
    ->json();

In this simple example, you get support for timeouts, retry and JSON parsing.

You want to specify a user agent, just add withUserAgent:

Http::timeout(5)
    ->retry(times: 3, sleep: 1)
    ->withUserAgent('My Super Cool User Agent')
    ->get($url)
    ->json();

Specifying a proper user agent when calling an external API is a great way to make debugging on that side easier.

If you want to get an exception when the call fails, just add the throw call:

Http::timeout(5)
    ->retry(times: 3, sleep: 1)
    ->withUserAgent('My Super Cool User Agent')
    ->throw()
    ->get($url)
    ->json();

You want concurrent requests, no problem either:

use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('first')->get('http://localhost/first'),
    $pool->as('second')->get('http://localhost/second'),
    $pool->as('third')->get('http://localhost/third'),
]);

return $responses['first']->ok();

If you want to configure common settings for request, just use macros:

namespace App\Providers;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Http::macro('github', function () {
            return Http::withHeaders([
                'X-Example' => 'example',
            ])->baseUrl('https://github.com');
        });
    }
}

Once setup, you can use it like this:

$response = Http::github()->get('/');

Even testing is easy as pie by using faking:

Http::fake([
    // Stub a JSON response for GitHub endpoints...
    'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),

    // Stub a string response for all other endpoints...
    '*' => Http::response('Hello World', 200, ['Headers']),
]);

You can then do your HTTP calls as normal and inspect them afterwards:

Http::withHeaders([
    'X-First' => 'foo',
])->post('http://example.com/users', [
    'name' => 'Taylor',
    'role' => 'Developer',
]);

Http::assertSent(function (Request $request) {
    return $request->hasHeader('X-First', 'foo') &&
           $request->url() == 'http://example.com/users' &&
           $request['name'] == 'Taylor' &&
           $request['role'] == 'Developer';
});

For me, I'm not using Guzzle (the library on which the HTTP client is based) never directly again…