Weave 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

Service Client Bundle Laravel Package

deeep/service-client-bundle

View on GitHub
Deep Wiki
Context7

Middleware

🌐 Language: English | Русский

The HTTP client uses a middleware pipeline for processing requests and responses.

Execution Order

Middleware are executed in descending priority order. Higher priority = executed earlier.

Request Flow (high → low priority):

┌─────────────────────────────────────────────────────────────────────┐
│                           HTTP Client                               │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [100] AuthMiddleware                                               │
│        Adds Authorization header                                    │
│        Why first: credentials must be added before retry            │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [90] RetryMiddleware                                               │
│       Retries request on errors (429, 5xx)                          │
│       Why here: retry should encompass logging and circuit          │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [80] LoggingMiddleware                                             │
│       Logs each request attempt                                     │
│       Why here: log each retry, but after auth                      │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [60] CircuitBreakerMiddleware                                      │
│       Blocks requests to "failed" services                          │
│       Why here: circuit considers each retry attempt                │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  [10] MultiHostMiddleware                                           │
│       Selects host by strategy (failover, round-robin, parallel)    │
│       Why last: executes immediately before transport               │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         GuzzleTransport                             │
│                    Sends HTTP request                               │
└─────────────────────────────────────────────────────────────────────┘

Built-in Middleware

Priority Middleware Purpose Enabled by Default
100 AuthMiddleware Adds authentication headers Yes
90 RetryMiddleware Retries with exponential backoff Yes (configurable)
80 LoggingMiddleware Logs requests/responses Yes (configurable)
60 CircuitBreakerMiddleware Circuit Breaker pattern No (by default)
10 MultiHostMiddleware Multi-host strategy handling Automatic

Why This Order?

AuthMiddleware (100) — First

Authentication must be added before any retry logic. If auth were added after retry:

  • First request would go without credentials
  • Retry would add auth repeatedly

RetryMiddleware (90) — After Auth

Retry should encompass all downstream middleware:

  • Log each attempt
  • Consider circuit breaker state
  • Try different hosts on failover

LoggingMiddleware (80) — Logs Each Attempt

Located inside the retry cycle, so:

  • Each attempt is logged, not just the final result
  • Credentials are already added (not logged separately)

CircuitBreakerMiddleware (60) — Cascade Failure Protection

Checks circuit state for each attempt:

  • If circuit is open — request is not sent
  • Failure/success are counted in statistics

MultiHostMiddleware (10) — Last

Host selection happens immediately before sending:

  • Failover tries next host on error
  • Round-robin rotates between hosts
  • Parallel sends to all hosts

Creating Custom Middleware

Middleware supports both synchronous and asynchronous requests through Promise-based architecture (like Guzzle).

<?php

declare(strict_types=1);

namespace App\Middleware;

use Deeep\ServiceClient\Context\RequestContext;
use Deeep\ServiceClient\Contract\MiddlewareInterface;
use Deeep\ServiceClient\Contract\RequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Throwable;

final readonly class TimingMiddleware implements MiddlewareInterface
{
    private const int PRIORITY = 85;

    public function __construct(
        private MetricsCollector $metrics,
    ) {}

    public function process(
        RequestInterface $request,
        RequestContext $context,
        callable $next,
    ): ResponseInterface|PromiseInterface {
        $startTime = microtime(true);

        try {
            $result = $next($request, $context);
        } catch (Throwable $e) {
            $this->recordMetrics($request, $startTime, false);
            throw $e;
        }

        if ($result instanceof PromiseInterface) {
            return $result->then(
                function (ResponseInterface $response) use ($request, $startTime): ResponseInterface {
                    $this->recordMetrics($request, $startTime, true);
                    return $response;
                },
                function (Throwable $e) use ($request, $startTime): void {
                    $this->recordMetrics($request, $startTime, false);
                    throw $e;
                },
            );
        }

        $this->recordMetrics($request, $startTime, true);

        return $result;
    }

    public function getPriority(): int
    {
        return self::PRIORITY;
    }

    private function recordMetrics(RequestInterface $request, float $startTime, bool $success): void
    {
        $this->metrics->recordTiming(
            service: $request->getService(),
            duration: microtime(true) - $startTime,
            success: $success,
        );
    }
}

Important: Promise-based Architecture

  • $next() returns ResponseInterface for sync or PromiseInterface for async
  • For async, handle result via ->then(onSuccess, onError)
  • The same middleware works for both synchronous and asynchronous requests
  • Synchronous exceptions are caught in try-catch, asynchronous — in onError callback

Registering Middleware

Automatic (Recommended)

Implement MiddlewareInterface — Symfony will automatically register the middleware:

// src/Middleware/TimingMiddleware.php
// Just implement MiddlewareInterface - autoconfiguration via instanceof

Manual Registration

<?php
// config/services.php

use App\Middleware\TimingMiddleware;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $container): void {
    $services = $container->services();

    $services->set(TimingMiddleware::class)
        ->tag('deeep_service_client.middleware');
};

Priority Recommendations

Range Purpose Examples
95-100 Request modification Auth, Request ID, Tenant headers
85-94 Metrics/timing (outside retry) Prometheus metrics, APM
75-89 Inside retry cycle Logging, tracing
50-74 Protection patterns Circuit breaker, rate limiting
1-49 Transport level Multi-host, caching

Disabling Built-in Middleware

Retry

'services' => [
    'fast_api' => [
        'host' => '...',
        'retry' => [
            'enabled' => false,
        ],
    ],
],

Circuit Breaker

Disabled by default. To enable:

'services' => [
    'unreliable_api' => [
        'host' => '...',
        'circuit_breaker' => [
            'enabled' => true,
            'failure_threshold' => 5,
            'open_timeout' => 30,
        ],
    ],
],

Logging

'services' => [
    'internal_api' => [
        'host' => '...',
        'logging' => [
            'enabled' => false,
        ],
    ],
],
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style