Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Services Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Update dependencies (required for compatibility):
    composer require guzzlehttp/guzzle:^7.13.3 guzzlehttp/psr7:^2.12.4 guzzlehttp/command:^1.5.2 guzzlehttp/uri-template:^1.0.9
    
  2. Define a service description (unchanged, but ensure PHP 8.6 compatibility):
    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'],
                ],
            ],
        ],
    ];
    
  3. Create a service client (updated for PHP 8.6 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]);
        }
    }
    
  4. Use in Laravel controllers (unchanged):
    use App\Services\UserService;
    
    class UserController extends Controller
    {
        public function show(int $id)
        {
            $user = UserService::make()->fetch($id);
            return response()->json($user);
        }
    }
    

First Use Case: API Wrapper for Third-Party Service

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.


Implementation Patterns

1. Service Description Organization

  • Centralize descriptions in Laravel config (e.g., config/api_descriptions.php) or use environment-specific files (e.g., config/api_descriptions/{env}.php).
  • Reuse models across operations to avoid duplication:
    'models' => [
        'User' => [...],
        'Order' => [...],
    ],
    'operations' => [
        'createOrder' => [
            'responseModel' => 'Order',
            // ...
        ],
        'updateUser' => [
            'responseModel' => 'User',
            // ...
        ],
    ],
    
  • Extend descriptions dynamically using Laravel’s merge config:
    $description = new Description(config('api_descriptions'));
    $description->addOperation('newOperation', [...]);
    

2. Dependency Injection in Laravel

  • Bind the 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"]]
            );
        });
    }
    
  • Inject services into controllers/services:
    use GuzzleHttp\Command\Guzzle\GuzzleClient;
    
    class PaymentService
    {
        public function __construct(private GuzzleClient $client) {}
    
        public function processPayment(array $data): array
        {
            return $this->client->charge($data);
        }
    }
    

3. Handling Complex Responses

  • Nested JSON responses:
    'models' => [
        'Product' => [
            'type' => 'object',
            'properties' => [
                'id' => ['type' => 'integer'],
                'reviews' => [
                    'type' => 'array',
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'rating' => ['type' => 'integer'],
                            'comment' => ['type' => 'string'],
                        ],
                    ],
                ],
            ],
        ],
    ],
    
  • Custom response parsing with filters:
    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()],
        ],
    ],
    

4. CLI Integration

  • Create artisan commands for API interactions:
    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);
        }
    }
    
  • Use for debugging or manual API testing during development.

5. Error Handling

  • Validate requests using the description’s parameter rules:
    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());
    }
    
  • Custom error responses:
    'operations' => [
        'getUser' => [
            'httpMethod' => 'GET',
            'uri' => '/users/{id}',
            'responseModel' => 'User',
            'errorModels' => [
                '404' => 'NotFoundError',
            ],
        ],
    ],
    

6. Testing

  • Mock 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"}');
        })
    );
    
  • Test validation:
    $this->expectException(\GuzzleHttp\Command\Guzzle\Exception\ValidationException::class);
    $client->createOrder(['invalid' => 'data']);
    

Gotchas and Tips

Pitfalls

  1. Parameter Location Confusion:

    • Gotcha: Mixing up uri, query, header, formParam, and multipart locations can lead to malformed requests.
    • Fix: Double-check the location field in your description. Use the Guzzle URI Template for dynamic URIs.
    • Example:
      'parameters' => [
          'api_key' => ['location' => 'header', 'type' => 'string'], // Header
          'limit' => ['location' => 'query', 'type' => 'integer'],    // Query
      ],
      
  2. Response Model Mismatches:

    • Gotcha: If the API response structure changes but your model description doesn’t, you’ll get parsing errors.
    • Fix: Use dynamic models or fallback handling:
      'models' => [
          'dynamicResponse' => [
              'type' => 'object',
              'additionalProperties' => ['location' => 'json'], //
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor