guzzlehttp/guzzle-services
Guzzle Services adds a command layer on top of Guzzle using service descriptions to define operations, serialize requests, and parse responses into convenient model structures. Build typed clients from descriptions, call operations as methods, and get structured results.
composer require guzzlehttp/guzzle:^7.13.3 guzzlehttp/psr7:^2.12.4 guzzlehttp/command:^1.5.2 guzzlehttp/uri-template:^1.0.9
return [
'baseUri' => 'https://api.example.com/v1',
'operations' => [
'getUser' => [
'httpMethod' => 'GET',
'uri' => '/users/{id}',
'responseModel' => 'User',
'parameters' => [
'id' => ['type' => 'integer', 'location' => 'uri'],
],
],
],
'models' => [
'User' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
],
],
],
];
trim behavior):
use GuzzleHttp\Client;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
class UserService
{
public function __construct(private GuzzleClient $client) {}
public static function make(): self
{
$description = new Description(config('api_descriptions'));
$guzzleClient = new GuzzleClient(
new Client(),
$description,
['trim_chars' => [' ', "\t", "\n", "\r", "\0", "\x0B"]] // Explicit trim config
);
return new self($guzzleClient);
}
public function fetch(int $id): array
{
return $this->client->getUser(['id' => $id]);
}
}
use App\Services\UserService;
class UserController extends Controller
{
public function show(int $id)
{
$user = UserService::make()->fetch($id);
return response()->json($user);
}
}
Leverage the package to create a type-safe client for a payment gateway (e.g., Stripe). Define the service description once, then reuse it across the application for consistency. The updated Guzzle version ensures compatibility with modern Laravel applications and PHP 8.6.
config/api_descriptions.php) or use environment-specific files (e.g., config/api_descriptions/{env}.php).'models' => [
'User' => [...],
'Order' => [...],
],
'operations' => [
'createOrder' => [
'responseModel' => 'Order',
// ...
],
'updateUser' => [
'responseModel' => 'User',
// ...
],
],
$description = new Description(config('api_descriptions'));
$description->addOperation('newOperation', [...]);
GuzzleClient to the container for easy access:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(GuzzleClient::class, function ($app) {
$description = new Description(config('api_descriptions'));
return new GuzzleClient(
new Client(),
$description,
['trim_chars' => [' ', "\t", "\n", "\r", "\0", "\x0B"]]
);
});
}
use GuzzleHttp\Command\Guzzle\GuzzleClient;
class PaymentService
{
public function __construct(private GuzzleClient $client) {}
public function processPayment(array $data): array
{
return $this->client->charge($data);
}
}
'models' => [
'Product' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'reviews' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'rating' => ['type' => 'integer'],
'comment' => ['type' => 'string'],
],
],
],
],
],
],
use GuzzleHttp\Command\Guzzle\Filter\FilterInterface;
class UppercaseFilter implements FilterInterface
{
public function __invoke(array $response): array
{
return array_map('strtoupper', $response);
}
}
// Apply filter in description:
'operations' => [
'getUser' => [
'responseModel' => 'User',
'responseFilters' => [new UppercaseFilter()],
],
],
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use Illuminate\Console\Command;
class TestApiCommand extends Command
{
protected $signature = 'api:test {operation} {--params=}';
protected $description = 'Test an API operation';
public function handle(GuzzleClient $client)
{
$params = json_decode($this->option('params'), true);
$result = $client->{$this->argument('operation')}($params);
$this->info($result);
}
}
try {
$result = $client->createOrder(['invalid' => 'data']);
} catch (\GuzzleHttp\Command\Guzzle\Exception\ValidationException $e) {
// Handle validation errors (e.g., log or return to user)
$this->error($e->getMessage());
}
'operations' => [
'getUser' => [
'httpMethod' => 'GET',
'uri' => '/users/{id}',
'responseModel' => 'User',
'errorModels' => [
'404' => 'NotFoundError',
],
],
],
GuzzleClient in tests:
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
$description = new Description([...]);
$client = new GuzzleClient(
new Client(),
$description,
['trim_chars' => [' ', "\t", "\n", "\r", "\0", "\x0B"]]
);
// Mock responses
$client->getHandlerStack()->push(
Middleware::mock(function ($request) {
return new Response(200, [], '{"id": 1, "name": "Test"}');
})
);
$this->expectException(\GuzzleHttp\Command\Guzzle\Exception\ValidationException::class);
$client->createOrder(['invalid' => 'data']);
Parameter Location Confusion:
uri, query, header, formParam, and multipart locations can lead to malformed requests.location field in your description. Use the Guzzle URI Template for dynamic URIs.'parameters' => [
'api_key' => ['location' => 'header', 'type' => 'string'], // Header
'limit' => ['location' => 'query', 'type' => 'integer'], // Query
],
Response Model Mismatches:
'models' => [
'dynamicResponse' => [
'type' => 'object',
'additionalProperties' => ['location' => 'json'], //
How can I help you explore Laravel packages today?