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

Command Laravel Package

guzzlehttp/command

Build higher-level web service clients on top of Guzzle by modeling operations as Commands and responses as Results. Includes a generic ServiceClient plus command middleware to map commands to PSR-7 requests and responses to structured results.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require guzzlehttp/command
    

    Add to composer.json if using Laravel’s require-dev or require section.

  2. Basic Client Initialization:

    use GuzzleHttp\Client;
    use GuzzleHttp\Command\ServiceClient;
    
    $httpClient = new Client(['base_uri' => 'https://api.example.com']);
    $serviceClient = new ServiceClient(
        $httpClient,
        fn($command) => new Request('POST', '/', [], json_encode($command->toArray())),
        fn($response) => new Result(json_decode($response->getBody(), true))
    );
    
  3. First Use Case: Execute a command with magic methods:

    $result = $serviceClient->getUser(['id' => 1]);
    dd($result['data']); // Access parsed response data
    

Key Files to Explore

  • app/Providers/AppServiceProvider.php: Bind the ServiceClient to Laravel’s container.
  • app/Http/Clients/: Create a dedicated directory for service clients (e.g., UserClient.php).
  • config/services.php: Store API base URIs and default configs.

Implementation Patterns

1. Service Client Organization

Pattern: Group related API endpoints into dedicated clients. Example:

// app/Http/Clients/StripeClient.php
namespace App\Http\Clients;

use GuzzleHttp\Command\ServiceClient;

class StripeClient extends ServiceClient
{
    public function __construct()
    {
        parent::__construct(
            new Client(['base_uri' => config('services.stripe.base_uri')]),
            // Command-to-Request transformer...
            // Response-to-Result transformer...
        );
    }

    // Magic methods for Stripe endpoints
    public function createCustomer(array $args) { /* ... */ }
}

Laravel Integration:

// app/Providers/AppServiceProvider.php
public function register()
{
    $this->app->singleton(StripeClient::class, fn($app) => new StripeClient());
}

2. Middleware for Cross-Cutting Concerns

Pattern: Use middleware for auth, logging, or request/response transformations. Example:

// Add auth middleware
$client->getHandlerStack()->push(function ($handler) {
    return function ($command) use ($handler) {
        $command['@http']['headers']['Authorization'] = 'Bearer ' . auth()->token();
        return $handler($command);
    };
});

Laravel-Specific:

// app/Http/Middleware/ApiAuthMiddleware.php
public function handle($request, Closure $next)
{
    $client = app(StripeClient::class);
    $client->getHandlerStack()->push(fn($handler) => function($command) use ($handler) {
        $command['@http']['headers']['Authorization'] = 'Bearer ' . $request->bearerToken();
        return $handler($command);
    });
    return $next($request);
}

3. Async Workflows with Promises

Pattern: Use executeAsync() for non-blocking calls (e.g., background jobs). Example:

// Dispatch a job with async command
CreateUserJob::dispatch($serviceClient->createUserAsync(['name' => 'John']));

Laravel Job:

// app/Jobs/CreateUserJob.php
public function handle()
{
    $promise = $this->serviceClient->createUserAsync($this->data);
    $result = $promise->wait(); // Block until resolved
    // Process $result
}

4. Concurrent Requests

Pattern: Batch API calls with executeAll() (e.g., fetching multiple users). Example:

$commands = [
    'user1' => $client->getUser(['id' => 1]),
    'user2' => $client->getUser(['id' => 2]),
];

$results = $client->executeAll($commands, ['concurrency' => 5]);
foreach ($results as $key => $result) {
    if ($result instanceof Result) {
        // Success
    } else {
        // Failure: $result is the exception
    }
}

5. Error Handling

Pattern: Catch CommandException for API errors. Example:

try {
    $result = $client->deleteUser(['id' => 1]);
} catch (\GuzzleHttp\Command\Exception\CommandException $e) {
    if ($e->getResponse()->getStatusCode() === 404) {
        // Handle not found
    }
}

Laravel Exception Handler:

// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
    if ($exception instanceof \GuzzleHttp\Command\Exception\CommandException) {
        return response()->json(['error' => 'API Error'], 500);
    }
    return parent::render($request, $exception);
}

Gotchas and Tips

Pitfalls

  1. @http Injection:

    • Never pass untrusted input to @http. Validate/reserve this key explicitly:
      if (array_key_exists('@http', $input)) {
          throw new \InvalidArgumentException('Forbidden key: @http');
      }
      
  2. Middleware Order:

    • Middleware runs top-to-bottom (LIFO). Place auth middleware last to avoid overriding headers.
  3. Async Deadlocks:

    • Avoid mixing wait() with Laravel’s sync queue workers. Use then() for callbacks:
      $promise->then(fn($result) => User::create($result['data']));
      
  4. Concurrency Limits:

    • Set concurrency wisely. Default (25) may overload APIs. Monitor with:
      $client->executeAllAsync($commands, ['concurrency' => 3]);
      

Debugging Tips

  1. Log Raw Requests/Responses:

    • Add Guzzle middleware to log:
      $client->getHandlerStack()->push(
          Middleware::tap(fn($request) => Log::debug('Request:', $request->toPsr()))
      );
      
  2. Inspect Commands:

    • Dump commands before execution:
      $command = $client->getCommand('foo', ['bar' => 'baz']);
      dd($command->toArray()); // Debug payload
      
  3. Handle Deprecated Guzzle:

    • If using Guzzle 7.x, ensure guzzlehttp/command is ^1.2.0+ for PSR-7 v2 support.

Extension Points

  1. Custom Results:

    • Extend Result to add metadata:
      class ApiResult extends Result {
          public function getRateLimit() { /* ... */ }
      }
      
  2. Dynamic URIs:

    • Use UriTemplate for dynamic paths:
      $request = new Request(
          'GET',
          UriTemplate::expand('/users/{id}', ['id' => $command['id']])
      );
      
  3. Laravel Facades:

    • Create a facade for cleaner syntax:
      // app/Facades/Api.php
      public static function getUser($id) {
          return app(StripeClient::class)->getUser(['id' => $id]);
      }
      
  4. Testing:

    • Mock ServiceClient with Mockery:
      $mock = Mockery::mock(ServiceClient::class);
      $mock->shouldReceive('getUser')->andReturn(new Result(['id' => 1]));
      

Config Quirks

  1. Base URI Overrides:

    • Override per-command with @http:
      $client->getUser(['id' => 1, '@http' => ['base_uri' => 'https://staging-api.com']]);
      
  2. JSON vs. Form Data:

    • Switch payload format via @http:
      $command['@http'] = ['headers' => ['Content-Type' => 'application/x-www-form-urlencoded']];
      
  3. HandlerStack vs. Guzzle Middleware:

    • Command middleware (e.g., auth) → ServiceClient’s HandlerStack.
    • HTTP middleware (e.g., retries) → Guzzle’s Client stack.
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.
terminal42/code-quality-tools
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