Cos’è l’HTTP Client
Il client HTTP di Laravel è una comoda API che avvolge Guzzle .
Con la facade Http scrivi in modo conciso richieste HTTP a servizi e API esterni.
use Illuminate\Support\Facades\ Http ;
$response = Http :: get ( 'https://api.example.com/users' );
Guzzle è già installato: puoi iniziare subito senza configurazioni.
Richieste di base
GET
use Illuminate\Support\Facades\ Http ;
$response = Http :: get ( 'https://api.example.com/users' );
Parametri di query come array.
$response = Http :: get ( 'https://api.example.com/users' , [
'page' => 1 ,
'per_page' => 20 ,
]);
POST
I dati sono inviati di default come application/json.
$response = Http :: post ( 'https://api.example.com/users' , [
'name' => 'Mario Rossi' ,
'email' => '[email protected] ' ,
]);
PUT / PATCH / DELETE
$response = Http :: put ( 'https://api.example.com/users/1' , [
'name' => 'Giulia Bianchi' ,
]);
$response = Http :: patch ( 'https://api.example.com/users/1' , [
'email' => '[email protected] ' ,
]);
$response = Http :: delete ( 'https://api.example.com/users/1' );
Gestione delle risposte
Http::get() e i metodi affini restituiscono un’istanza Illuminate\Http\Client\Response con molti metodi per ispezionare la risposta.
$response = Http :: get ( 'https://api.example.com/users/1' );
$response -> body (); // Body come stringa
$response -> json (); // In array
$response -> json ( 'name' ); // Chiave specifica
$response -> object (); // stdClass
$response -> collect (); // Collection
$response -> status ();
$response -> successful (); // 2xx
$response -> failed (); // 4xx+
$response -> clientError (); // 4xx
$response -> serverError (); // 5xx
$response -> ok (); // 200
$response -> created (); // 201
$response -> noContent (); // 204
$response -> notFound (); // 404
$response -> unauthorized (); // 401
$response -> forbidden (); // 403
$response -> unprocessableEntity (); // 422
$response -> tooManyRequests (); // 429
Puoi accedere alla risposta JSON anche come array.
$name = Http :: get ( 'https://api.example.com/users/1' )[ 'name' ];
Opzioni di richiesta
$response = Http :: withHeaders ([
'X-Api-Version' => '2' ,
'Accept-Language' => 'it' ,
]) -> get ( 'https://api.example.com/users' );
Per accettare application/json è comodo acceptJson().
$response = Http :: acceptJson () -> get ( 'https://api.example.com/users' );
Per gli header CSRF (X-CSRF-TOKEN / X-XSRF-TOKEN) delle richieste AJAX dal browser verso Laravel, consulta Protezione CSRF .
Autenticazione
Bearer token (il più comune):
$response = Http :: withToken ( $token ) -> get ( 'https://api.example.com/me' );
Basic auth :
$response = Http :: withBasicAuth ( '[email protected] ' , 'password' )
-> get ( 'https://api.example.com/private' );
Base URL
Se richiami spesso lo stesso host, usa baseUrl().
$response = Http :: baseUrl ( 'https://api.example.com' )
-> withToken ( $token )
-> get ( '/users/1' );
Per application/x-www-form-urlencoded usa asForm().
$response = Http :: asForm () -> post ( 'https://api.example.com/login' , [
'username' => 'mario' ,
'password' => 'secret' ,
]);
Timeout
// Timeout di risposta (default 30s)
$response = Http :: timeout ( 10 ) -> get ( 'https://api.example.com/slow-endpoint' );
// Timeout di connessione (default 10s)
$response = Http :: connectTimeout ( 5 ) -> get ( 'https://api.example.com/endpoint' );
In caso di timeout viene sollevata Illuminate\Http\Client\ConnectionException.
Imposta sempre timeout quando chiami API esterne.
Retry
$response = Http :: retry ( 3 , 100 ) -> post ( 'https://api.example.com/orders' , $data );
Retry condizionali:
use Illuminate\Http\Client\ PendingRequest ;
use Throwable ;
use Illuminate\Http\Client\ ConnectionException ;
$response = Http :: retry ( 3 , 100 , function ( Throwable $exception , PendingRequest $request ) {
return $exception instanceof ConnectionException ;
}) -> post ( 'https://api.example.com/orders' , $data );
Gestione degli errori
Verifica manuale
Di default il client HTTP non solleva eccezioni per 4xx/5xx. Verifica esplicitamente.
$response = Http :: get ( 'https://api.example.com/users/999' );
if ( $response -> notFound ()) {
// 404
}
if ( $response -> failed ()) {
logger () -> error ( 'API request failed' , [ 'status' => $response -> status ()]);
}
Sollevare eccezioni
throw() solleva Illuminate\Http\Client\RequestException in caso di errore.
$response = Http :: post ( 'https://api.example.com/users' , $data ) -> throw ();
$response = Http :: get ( 'https://api.example.com/users/1' );
$response -> throwIf ( $response -> status () === 422 );
$response = Http :: post ( 'https://api.example.com/orders' , $data );
$response -> throwUnlessStatus ( 201 );
throw() restituisce la Response per la concatenazione.
$user = Http :: post ( 'https://api.example.com/users' , $data )
-> throw ()
-> json ();
Gestione dell’eccezione:
use Illuminate\Http\Client\ RequestException ;
use Illuminate\Http\Client\ ConnectionException ;
try {
$response = Http :: timeout ( 5 )
-> post ( 'https://api.example.com/users' , $data )
-> throw ();
} catch ( ConnectionException $e ) {
logger () -> error ( 'Connection failed: ' . $e -> getMessage ());
} catch ( RequestException $e ) {
logger () -> error ( 'API error' , [ 'status' => $e -> response -> status ()]);
}
Richieste parallele
Per chiamare più API in parallelo usa pool().
use Illuminate\Http\Client\ Pool ;
use Illuminate\Support\Facades\ Http ;
$responses = Http :: pool ( fn ( Pool $pool ) => [
$pool -> as ( 'users' ) -> get ( 'https://api.example.com/users' ),
$pool -> as ( 'posts' ) -> get ( 'https://api.example.com/posts' ),
$pool -> as ( 'comments' ) -> get ( 'https://api.example.com/comments' ),
]);
$users = $responses [ 'users' ] -> json ();
$posts = $responses [ 'posts' ] -> json ();
$comments = $responses [ 'comments' ] -> json ();
Molto più veloce rispetto all’esecuzione sequenziale. Utile in dashboard che chiamano più API.
Test
Mock con Http::fake()
Nei test usa Http::fake() per simulare le risposte senza chiamate reali.
use Illuminate\Support\Facades\ Http ;
Http :: fake ();
$response = Http :: get ( 'https://api.example.com/users' );
$response -> successful (); // true
Mock per URL specifici:
Http :: fake ([
'api.example.com/users/*' => Http :: response ([ 'id' => 1 , 'name' => 'Mario Rossi' ], 200 ),
'api.example.com/posts/*' => Http :: response ([ 'error' => 'Not Found' ], 404 ),
'*' => Http :: response ( 'OK' , 200 ),
]);
Sequenze di risposte:
Http :: fake ([
'api.example.com/*' => Http :: sequence ()
-> push ([ 'id' => 1 ], 200 )
-> push ([ 'id' => 2 ], 200 )
-> pushStatus ( 429 ),
]);
Verifica delle richieste
Con Http::assertSent() verifichi il contenuto delle richieste.
use Illuminate\Http\Client\ Request ;
use Illuminate\Support\Facades\ Http ;
Http :: fake ();
Http :: withToken ( 'my-token' ) -> post ( 'https://api.example.com/users' , [
'name' => 'Mario Rossi' ,
]);
Http :: assertSent ( function ( Request $request ) {
return $request -> url () === 'https://api.example.com/users'
&& $request -> hasHeader ( 'Authorization' , 'Bearer my-token' )
&& $request [ 'name' ] === 'Mario Rossi' ;
});
Http :: assertNotSent ( function ( Request $request ) {
return $request -> url () === 'https://api.example.com/admin' ;
});
Http :: assertSentCount ( 1 );
Chiama sempre Http::fake() all’inizio del test. Con Http::preventStrayRequests() fai in modo che le richieste non mockate lancino eccezioni.
Prevenire richieste sfuggite
Http :: preventStrayRequests ();
Http :: fake ([
'api.example.com/*' => Http :: response ([ 'ok' => true ]),
]);
Http :: get ( 'https://other.example.com/endpoint' ); // Lancia eccezione
Esempio pratico: classe di servizio per chiamate API
Crea la classe di servizio
<? php
namespace App\Services ;
use Illuminate\Http\Client\ RequestException ;
use Illuminate\Http\Client\ ConnectionException ;
use Illuminate\Support\Facades\ Http ;
class GitHubService
{
private string $baseUrl = 'https://api.github.com' ;
public function __construct (
private readonly string $token ,
) {}
public function getUser ( string $username ) : array
{
return Http :: baseUrl ( $this -> baseUrl )
-> withToken ( $this -> token )
-> acceptJson ()
-> timeout ( 10 )
-> get ( "/users/{ $username }" )
-> throw ()
-> json ();
}
public function getRepositories ( string $username , int $page = 1 ) : array
{
return Http :: baseUrl ( $this -> baseUrl )
-> withToken ( $this -> token )
-> acceptJson ()
-> timeout ( 10 )
-> retry ( 2 , 500 )
-> get ( "/users/{ $username }/repos" , [
'page' => $page ,
'per_page' => 30 ,
'sort' => 'updated' ,
])
-> throw ()
-> json ();
}
}
Registralo nel service provider
use App\Services\ GitHubService ;
public function register () : void
{
$this -> app -> singleton ( GitHubService :: class , function () {
return new GitHubService (
token : config ( 'services.github.token' ),
);
});
}
// config/services.php
'github' => [
'token' => env ( 'GITHUB_TOKEN' ),
],
Usalo nel controller
<? php
namespace App\Http\Controllers ;
use App\Services\ GitHubService ;
use Illuminate\Http\Client\ RequestException ;
use Illuminate\Http\Client\ ConnectionException ;
use Illuminate\Http\ JsonResponse ;
class GitHubController extends Controller
{
public function __construct (
private readonly GitHubService $github ,
) {}
public function show ( string $username ) : JsonResponse
{
try {
$user = $this -> github -> getUser ( $username );
return response () -> json ( $user );
} catch ( ConnectionException ) {
return response () -> json ([ 'error' => 'Impossibile connettersi alla GitHub API.' ], 503 );
} catch ( RequestException $e ) {
$status = $e -> response -> status ();
if ( $status === 404 ) {
return response () -> json ([ 'error' => 'Utente non trovato.' ], 404 );
}
return response () -> json ([ 'error' => 'Errore della GitHub API.' ], 502 );
}
}
}
Scrivi i test
<? php
namespace Tests\Unit\Services ;
use App\Services\ GitHubService ;
use Illuminate\Http\Client\ ConnectionException ;
use Illuminate\Http\Client\ RequestException ;
use Illuminate\Support\Facades\ Http ;
use Tests\ TestCase ;
class GitHubServiceTest extends TestCase
{
private GitHubService $service ;
protected function setUp () : void
{
parent :: setUp ();
Http :: fake ([
'api.github.com/users/octocat' => Http :: response ([
'login' => 'octocat' ,
'name' => 'The Octocat' ,
'public_repos' => 8 ,
], 200 ),
'api.github.com/users/notfound' => Http :: response (
[ 'message' => 'Not Found' ],
404
),
]);
$this -> service = new GitHubService ( token : 'test-token' );
}
public function test_ottiene_utente () : void
{
$user = $this -> service -> getUser ( 'octocat' );
$this -> assertEquals ( 'octocat' , $user [ 'login' ]);
$this -> assertEquals ( 'The Octocat' , $user [ 'name' ]);
Http :: assertSent ( function ( $request ) {
return $request -> url () === 'https://api.github.com/users/octocat'
&& $request -> hasHeader ( 'Authorization' , 'Bearer test-token' );
});
}
public function test_utente_non_trovato_lancia_eccezione () : void
{
$this -> expectException ( RequestException :: class );
$this -> service -> getUser ( 'notfound' );
}
}
Riepilogo
Metodo Uso Http::get($url, $query)GET Http::post($url, $data)POST JSON Http::put($url, $data)PUT Http::patch($url, $data)PATCH Http::delete($url)DELETE ->withToken($token)Bearer ->withHeaders($headers)Header ->timeout($seconds)Timeout ->retry($times, $sleep)Retry ->throw()Eccezione su errore Http::fake()Mock per test Http::pool($callback)Parallelo
Metodi di verifica della risposta
Metodo Descrizione successful()2xx failed()4xx+ clientError()4xx serverError()5xx ok()200 created()201 notFound()404 unauthorized()401 forbidden()403 unprocessableEntity()422 tooManyRequests()429
Best practice per API esterne
Raccogli la logica HTTP in una service class
Imposta sempre timeout (timeout() e connectTimeout())
Configura retry per errori transitori (retry())
Nei test usa Http::fake() per evitare chiamate reali
Aggiungi Http::preventStrayRequests() nel setup dei test
Gestisci token e credenziali con variabili d’ambiente e config/services.php