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

Http Client Contracts Laravel Package

symfony/http-client-contracts

Symfony HttpClient Contracts provides stable interfaces for HTTP clients and responses, extracted from Symfony. Build libraries against these battle-tested abstractions and swap implementations easily while staying compatible with Symfony’s HttpClient ecosystem.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package** (interfaces only):
   ```bash
   composer require symfony/http-client-contracts
  1. Pair with a concrete client (choose one):

    • Symfony’s native client (recommended for Laravel 10+):
      composer require symfony/http-client
      
    • Guzzle (with PSR-18 bridge):
      composer require guzzlehttp/guzzle guzzlehttp/psr7
      
    • PHP-HTTP Discovery (auto-detects installed clients):
      composer require php-http/discovery
      
  2. First use case: Replace hardcoded HTTP calls in a Laravel service with the contract:

    use Symfony\Contracts\HttpClient\HttpClientInterface;
    
    class ApiService
    {
        public function __construct(private HttpClientInterface $client) {}
    
        public function fetchData(string $endpoint): array
        {
            $response = $this->client->request('GET', "https://api.example.com/$endpoint");
            return $response->toArray(); // Returns decoded JSON
        }
    }
    
  3. Register the client in Laravel: Add to config/services.php:

    'http_client' => [
        'default' => Symfony\Component\HttpClient\HttpClient::class,
    ],
    

    Bind the interface in AppServiceProvider:

    $this->app->bind(HttpClientInterface::class, function ($app) {
        return new HttpClient();
    });
    

Implementation Patterns

Dependency Injection & Service Binding

  • Laravel-specific: Use the HttpClientInterface in constructors and let Laravel resolve it via the bound concrete client.
    // In a controller or service
    public function __construct(private HttpClientInterface $client) {}
    
  • Dynamic clients: Create scoped clients for different APIs:
    $client = HttpClient::create([
        'base_uri' => 'https://api.example.com/v1/',
        'headers' => ['Authorization' => 'Bearer ' . $token],
    ]);
    

Response Handling

  • Standardized response processing:
    $response = $client->request('GET', '/users');
    $status = $response->getStatusCode(); // 200, 404, etc.
    $content = $response->getContent(false); // Raw body
    $data = $response->toArray(); // Decoded JSON
    $headers = $response->getHeaders(); // ['content-type' => ['application/json']]
    
  • Error handling:
    try {
        $response = $client->request('GET', '/protected', [
            'throw' => true, // Throws HttpClientException on non-2xx
        ]);
    } catch (HttpClientException $e) {
        logger()->error('API request failed', ['error' => $e->getMessage()]);
    }
    

Testing Strategies

  • Unit tests: Mock HttpClientInterface with MockResponse:
    $mockResponse = MockResponse::fromJsonString('{"status": "success"}');
    $mockClient = $this->createMock(HttpClientInterface::class);
    $mockClient->method('request')->willReturn($mockResponse);
    
  • Integration tests: Use TestHttpClient (from symfony/http-client):
    use Symfony\Component\HttpClient\Test\MockHttpClient;
    
    $client = new MockHttpClient([
        'GET /users' => ['status' => 200, 'body' => '{"id": 1}'],
    ]);
    

Middleware & Decorators

  • Add cross-cutting concerns (e.g., logging, retries):
    class RetryHttpClient implements HttpClientInterface
    {
        public function __construct(private HttpClientInterface $client) {}
    
        public function request(string $method, string $url, array $options = []): ResponseInterface
        {
            $retries = $options['retries'] ?? 3;
            $lastException = null;
    
            for ($i = 0; $i < $retries; $i++) {
                try {
                    return $this->client->request($method, $url, $options);
                } catch (HttpClientException $e) {
                    $lastException = $e;
                    sleep(2 ** $i); // Exponential backoff
                }
            }
            throw $lastException;
        }
    }
    

Async Support (Optional)

  • Use Symfony’s AsyncHttpClient or PSR-18 async clients:
    $asyncClient = new AsyncHttpClient();
    $promise = $asyncClient->request('GET', 'https://api.example.com');
    $response = $promise->await(); // Blocks until response
    

Gotchas and Tips

Common Pitfalls

  1. Missing Concrete Client:

    • Error: Class 'Symfony\Component\HttpClient\HttpClient' not found.
    • Fix: Install symfony/http-client or another PSR-18-compatible client.
    • Laravel-specific: Ensure HttpClientInterface is bound in AppServiceProvider.
  2. Response Method Assumptions:

    • toArray(), getContent(), and getHeaders() throw exceptions for non-2xx responses.
    • Fix: Check status first or use ['throw' => false]:
      $response = $client->request('GET', '/error', ['throw' => false]);
      if ($response->getStatusCode() !== 200) {
          // Handle error
      }
      
  3. Async Confusion:

    • The contract supports async via ResponseInterface::until() and cancel(), but basic request() calls are synchronous unless using an async client.
    • Fix: Use symfony/http-client's AsyncHttpClient or guzzlehttp/promises.
  4. Laravel Facade Collisions:

    • Avoid mixing HttpClientInterface with Laravel’s Http facade (which uses Guzzle under the hood).
    • Fix: Stick to HttpClientInterface for consistency.

Debugging Tips

  • Enable Symfony HTTP Client Debug:
    $client = HttpClient::create([
        'debug' => true, // Logs all requests/responses
    ]);
    
  • Inspect Headers/Body:
    $response = $client->request('GET', '/debug');
    logger()->debug('Response Headers', $response->getHeaders());
    logger()->debug('Response Body', $response->getContent());
    

Performance Optimization

  • Connection Pooling:
    $client = HttpClient::create([
        'pooling' => true, // Reuses connections for multiple requests
    ]);
    
  • Timeouts:
    $client->request('GET', '/slow', [
        'timeout' => 5.0, // Seconds
    ]);
    

Extension Points

  1. Custom Response Decorators:

    • Extend ResponseInterface for domain-specific logic:
      class ApiResponse implements ResponseInterface
      {
          public function __construct(private ResponseInterface $response) {}
      
          public function toDomainModel(): User
          {
              $data = $this->response->toArray();
              return new User($data['id'], $data['name']);
          }
      }
      
  2. PSR-18 Interoperability:

    • Use symfony/psr-http-message-bridge to bridge Symfony messages with PSR-7/18:
      composer require symfony/psr-http-message-bridge
      
    • Convert between Symfony and PSR-7 messages:
      use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
      use Psr\Http\Message\RequestInterface;
      
      $symfonyRequest = new SymfonyRequest();
      $psrRequest = $symfonyRequest->toPsrRequest();
      
  3. Laravel-Specific Integrations:

    • Use Laravel\HttpClient\AsyncHttpClient (Laravel 10+) for async support:
      use Laravel\HttpClient\AsyncHttpClient;
      
      $asyncClient = new AsyncHttpClient();
      
    • Bind Laravel’s Http client to HttpClientInterface (if needed):
      $this->app->bind(HttpClientInterface::class, function () {
          return Http::client(); // Laravel 9+
      });
      

Configuration Quirks

  • Base URIs:
    • Prefer scoped clients over base URIs in every request:
      $client = HttpClient::create([
          'base_uri' => 'https://api.example.com/v1/',
      ]);
      $response = $client->request('GET', '/users'); // Resolves to https://api.example.com/v1/users
      
  • Environment Variables:
    • Avoid hardcoding secrets. Use Laravel’s .env:
      $client = HttpClient::create([
          'headers' => ['Authorization' => 'Bearer ' . env('API_TOKEN')],
      ]);
      

Laravel-Specific Workflows

  1. **Using
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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony