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

Psr 18 Guzzle Adapter Laravel Package

jord-jd/psr-18-guzzle-adapter

Simple PSR-18 adapter for the Guzzle HTTP client, letting you use Guzzle wherever a PSR-18 (ClientInterface) implementation is required. Lightweight, focused package for bridging PSR-compliant libraries with Guzzle.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to Begin

  1. Install the Package

    composer require jord-jd/psr-18-guzzle-adapter
    
  2. Bind the Adapter in Laravel In AppServiceProvider@register():

    use JordJD\Psr18GuzzleAdapter\GuzzleAdapter;
    use GuzzleHttp\Client;
    
    $this->app->singleton(\Psr\Http\Client\ClientInterface::class, function ($app) {
        return new GuzzleAdapter(new Client([
            'base_uri' => 'https://api.example.com',
        ]));
    });
    
  3. First PSR-18 Request

    use Psr\Http\Message\RequestInterface;
    use Psr\Http\Client\ClientInterface;
    
    public function __construct(private ClientInterface $client) {}
    
    public function fetchData()
    {
        $request = new \GuzzleHttp\Psr7\Request('GET', '/data');
        $response = $this->client->sendRequest($request);
        return json_decode($response->getBody(), true);
    }
    

Where to Look First

  • Adapter Source: GuzzleAdapter.php – Understand how Guzzle methods map to PSR-18.
  • PSR-18 Interface: ClientInterface – Reference for available methods.
  • Laravel HTTP Client: Compare with Laravel’s built-in PSR-18 client (Illuminate\Support\Facades\Http) for consistency.

Implementation Patterns

Dependency Injection in Laravel

Service Provider Binding

// app/Providers/AppServiceProvider.php
public function register()
{
    $this->app->bind(\Psr\Http\Client\ClientInterface::class, function ($app) {
        return new GuzzleAdapter(
            new \GuzzleHttp\Client($app['config']['http.client'])
        );
    });
}

Constructor Injection

use Psr\Http\Client\ClientInterface;

class ApiService
{
    public function __construct(private ClientInterface $client) {}

    public function getUser(int $id)
    {
        $request = new \GuzzleHttp\Psr7\Request('GET', "/users/{$id}");
        $response = $this->client->sendRequest($request);
        return json_decode($response->getBody(), true);
    }
}

Request Construction

Using PSR-7 Requests

use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;

public function createRequest(): RequestInterface
{
    return new Request('POST', '/endpoint', [
        'Content-Type' => 'application/json',
    ], json_encode(['key' => 'value']));
}

Reusing Guzzle’s Request Factory

use GuzzleHttp\Psr7\Request;

public function __construct(private GuzzleAdapter $adapter) {}

public function send()
{
    $request = $this->adapter->getAdapter()->createRequest('GET', '/data');
    return $this->adapter->sendRequest($request);
}

Response Handling

Streaming Responses

public function streamResponse(ClientInterface $client)
{
    $request = new Request('GET', '/large-file');
    $response = $client->sendRequest($request);

    while (!$response->getBody()->eof()) {
        echo $response->getBody()->read(1024);
    }
}

Error Handling

use Psr\Http\Client\ClientExceptionInterface;

public function safeRequest(ClientInterface $client)
{
    try {
        $response = $client->sendRequest(new Request('GET', '/data'));
        return json_decode($response->getBody(), true);
    } catch (ClientExceptionInterface $e) {
        // Handle Guzzle-specific exceptions (wrapped in PSR-18)
        return ['error' => $e->getMessage()];
    }
}

Middleware Integration

PSR-15 Middleware Stack

use League\Pipeline\Pipeline;
use Psr\Http\Client\Middleware;

$pipeline = new Pipeline([
    new class implements Middleware {
        public function __invoke(callable $handler, RequestInterface $request) {
            // Add auth header
            $request = $request->withHeader('Authorization', 'Bearer token');
            return $handler($request);
        }
    },
]);

$client = new GuzzleAdapter(
    new \GuzzleHttp\Client(),
    $pipeline
);

Guzzle Middleware (Limited Support)

// Note: Not all Guzzle middleware is PSR-18 compatible
$client = new GuzzleAdapter(
    new \GuzzleHttp\Client([
        'middleware' => [
            new \GuzzleHttp\Middleware::tap(function ($request) {
                // Pre-request logic
            }),
        ],
    ])
);

Testing with Mocks

Mocking PSR-18 Client

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

$mockClient = \Mockery::mock(ClientInterface::class);
$mockClient->shouldReceive('sendRequest')
    ->withArgs(function (RequestInterface $request) {
        return $request->getUri()->getPath() === '/data';
    })
    ->andReturn(new \GuzzleHttp\Psr7\Response(200, [], '{"id": 1}'));

$this->app->instance(ClientInterface::class, $mockClient);

Pest Test Example

it('fetches data via PSR-18 client', function () {
    $client = \Mockery::mock(ClientInterface::class);
    $client->shouldReceive('sendRequest')
        ->once()
        ->andReturn(new \GuzzleHttp\Psr7\Response(200, [], '{"test": true}'));

    $this->app->instance(ClientInterface::class, $client);

    $service = new ApiService($this->app->make(ClientInterface::class));
    expect($service->fetchData())->toBe(['test' => true]);
});

Gotchas and Tips

Common Pitfalls

  1. Guzzle-Specific Features Unavailable

    • Issue: Guzzle’s event system, retry middleware, or TransferStats are not exposed via PSR-18.
    • Fix: Use the underlying Guzzle client directly when needed:
      $guzzleClient = $adapter->getAdapter();
      $guzzleClient->getEmitter()->getEvents();
      
  2. Middleware Mismatch

    • Issue: PSR-15 middleware may not replicate Guzzle middleware behavior (e.g., retry logic).
    • Fix: Test critical flows and add custom middleware to bridge gaps.
  3. Response Body Consumption

    • Issue: PSR-18 responses require manual body consumption (unlike Guzzle’s fluent interface).
    • Fix: Always call $response->getBody()->getContents() or iterate the stream:
      $body = $response->getBody();
      while (!$body->eof()) {
          echo $body->read(1024);
      }
      
  4. Type Hinting Conflicts

    • Issue: IDE may complain about RequestInterface not implementing PSR-7 methods (e.g., withHeader).
    • Fix: Use GuzzleHttp\Psr7\Request for type safety:
      $request = new \GuzzleHttp\Psr7\Request('GET', '/data');
      
  5. Async Requests

    • Issue: PSR-18 lacks async support (unlike Guzzle’s promises).
    • Fix: Use the underlying Guzzle client for async:
      $promise = $adapter->getAdapter()->sendAsync($request);
      

Debugging Tips

  1. Log Requests/Responses

    $request = new Request('GET', '/data');
    $response = $client->sendRequest($request);
    
    \Log::debug('Request:', [
        'method' => $request->getMethod(),
        'uri' => (string) $request->getUri(),
        'headers' => $request->getHeaders(),
    ]);
    
  2. Inspect Guzzle Internals

    $guzzleClient = $adapter->getAdapter();
    $guzzleClient->getConfig('debug'); // Enable debug mode
    
  3. Handle Exceptions Gracefully

    try {
        $response = $client->sendRequest($request);
    } catch (\Psr\Http\Client\ClientExceptionInterface $e) {
        \Log::error('HTTP Client Error:', [
            'message' => $e->getMessage(),
            'request' => $request,
        ]);
        throw new \RuntimeException('API request failed', 0, $e);
    }
    

Configuration Quirks

  1. Base URI Handling
    • PSR-18 does not support Guzzle’s base_uri config. Set it manually:
      $request = new Request('GET', '/data');
      
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