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:

<?php
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:

<?php
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:

<?php
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:

<?php
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:

<?php
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:

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

Even testing is easy as pie by using faking:

<?php
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:

<?php
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…