deeep/service-client-bundle 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

deeep/service-client-bundle

Composer 安装命令:

composer require deeep/service-client-bundle

包简介

Symfony bundle: HTTP client with retry, multi-host, circuit breaker, and async support via Fibers

README 文档

README

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

Symfony bundle: HTTP client with retry, multi-host failover, circuit breaker, and async support via PHP Fibers.

Features

  • Retry with exponential backoff — configurable delay, jitter, and status codes
  • Multi-host strategies — failover, round-robin, parallel
  • Circuit breaker — automatic failure detection and recovery
  • Async via Fibers — non-blocking requests with Revolt event loop
  • Middleware pipeline — extensible request/response processing
  • Multiple auth methods — Bearer, API Key, Basic
  • Typed requests — ISP-based interfaces with response mapping

Requirements

  • PHP 8.4+
  • Symfony 7.4+ | 8.0+
  • Guzzle 7.9+

Installation

composer require deeep/service-client-bundle

Register the bundle in config/bundles.php:

return [
    // ...
    Deeep\ServiceClient\ServiceClientBundle::class => ['all' => true],
];

Configuration

<?php
// config/packages/deeep_service_client.php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'deeep_service_client' => [
        'defaults' => [
            'timeout' => [
                'connect' => 5.0,
                'request' => 30.0,
            ],
            'retry' => [
                'enabled' => true,
                'max_attempts' => 3,
                'delay' => 1000,
                'multiplier' => 2.0,
                'jitter' => 0.1,
            ],
        ],

        'services' => [
            'my_api' => [
                'host' => '%env(resolve:MY_API_HOST)%',
                'auth' => [
                    'type' => 'bearer',
                    'credentials' => [
                        'token' => '%env(resolve:MY_API_TOKEN)%',
                    ],
                ],
            ],

            'resilient_api' => [
                'hosts' => [
                    'strategy' => 'failover',
                    'list' => [
                        ['url' => '%env(resolve:API_PRIMARY)%'],
                        ['url' => '%env(resolve:API_BACKUP)%'],
                    ],
                ],
            ],
        ],
    ],
]);

Usage

Typed Request (Recommended)

use Deeep\ServiceClient\Contract\AuthRequiredInterface;
use Deeep\ServiceClient\Contract\MappableRequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use Deeep\ServiceClient\Enum\HttpMethod;
use Deeep\ServiceClient\Request\AbstractRequest;

/**
 * @implements MappableRequestInterface<User>
 */
final readonly class GetUserRequest extends AbstractRequest implements
    AuthRequiredInterface,
    MappableRequestInterface
{
    public function __construct(
        private int $userId,
    ) {}

    public function getService(): string
    {
        return 'my_api';
    }

    public function getMethod(): HttpMethod
    {
        return HttpMethod::GET;
    }

    public function getUri(): string
    {
        return "/users/{$this->userId}";
    }

    /**
     * @param ResponseInterface<mixed> $response
     */
    public function mapResponse(ResponseInterface $response): User
    {
        $data = $response->toArray();

        return new User($data['id'], $data['name'], $data['email']);
    }
}

// Usage
$request = new GetUserRequest(123);
$response = $httpClient->send($request);
$user = $response->getData(); // User

POST Request with Body

use Deeep\ServiceClient\Contract\AuthRequiredInterface;
use Deeep\ServiceClient\Contract\MappableRequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use Deeep\ServiceClient\Enum\HttpMethod;
use Deeep\ServiceClient\Request\AbstractRequest;

/**
 * @implements MappableRequestInterface<User>
 */
final readonly class CreateUserRequest extends AbstractRequest implements
    AuthRequiredInterface,
    MappableRequestInterface
{
    public function __construct(
        private string $name,
        private string $email,
    ) {}

    public function getService(): string
    {
        return 'my_api';
    }

    public function getMethod(): HttpMethod
    {
        return HttpMethod::POST;
    }

    public function getUri(): string
    {
        return '/users';
    }

    /**
     * @return array{name: string, email: string}
     */
    public function getBody(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }

    /**
     * @param ResponseInterface<mixed> $response
     */
    public function mapResponse(ResponseInterface $response): User
    {
        $data = $response->toArray();

        return new User($data['id'], $data['name'], $data['email']);
    }
}

Request Builder (Quick Requests)

$request = $requestBuilder
    ->service('my_api')
    ->get('/users')
    ->query(['limit' => 10])
    ->build();

$response = $httpClient->send($request);
$users = $response->toArray();

The builder is immutable — each method returns a new instance:

$base = $requestBuilder->service('api')->header('X-Tenant', 'acme');
$request1 = $base->get('/users')->build();
$request2 = $base->get('/orders')->build();  // Both have X-Tenant header

Async Requests

// Launch multiple requests
$results = [];
foreach ($userIds as $id) {
    $results[$id] = $httpClient->sendAsync(new GetUserRequest($id));
}

// Await all
$responses = $httpClient->await(...$results);

foreach ($responses as $id => $response) {
    $users[$id] = $response->getData();
}

Callbacks

$httpClient->sendAsync(
    $request,
    onResponse: fn($response) => $this->process($response),
    onError: fn($e) => $this->logger->error($e->getMessage()),
);

Error Response Callbacks

Use onErrorResponse() for handling 4xx/5xx responses separately from network errors:

$result = $httpClient->sendAsync($request);

$result
    ->then(fn($response) => $this->handleSuccess($response))
    ->onErrorResponse(fn($response) => $this->handleApiError($response))
    ->then(null, fn($e) => $this->handleNetworkError($e));

$response = $result->await();
  • then($onSuccess) — called for 2xx responses
  • onErrorResponse($callback) — called for 4xx/5xx responses with body
  • then(null, $onError) — called for network errors (timeout, connection failed)

Request Interfaces (ISP)

Interface Purpose
RequestInterface Base request (service, method, uri, headers, query, body)
AuthRequiredInterface Marker: request requires authentication
ConfigurableRequestInterface Override timeout/retry for specific request
MappableRequestInterface<T> Custom response mapping to type T
ErrorMappableRequestInterface<E> Custom error response (4xx/5xx) mapping to type E

Also available: BaseRequest — base class with default implementations for getHeaders(), getQuery(), getBody().

Handling Error Responses

Implement ErrorMappableRequestInterface for custom mapping of error responses (4xx/5xx):

use Deeep\ServiceClient\Contract\ErrorMappableRequestInterface;
use Deeep\ServiceClient\Contract\MappableRequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use Deeep\ServiceClient\Request\AbstractRequest;

/**
 * @implements MappableRequestInterface<User>
 * @implements ErrorMappableRequestInterface<ApiError>
 */
final readonly class GetUserRequest extends AbstractRequest implements
    MappableRequestInterface,
    ErrorMappableRequestInterface
{
    // ... getService(), getMethod(), getUri()

    /**
     * @param ResponseInterface<mixed> $response
     */
    public function mapResponse(ResponseInterface $response): User
    {
        $data = $response->toArray();

        return new User($data['id'], $data['name'], $data['email']);
    }

    /**
     * @param ResponseInterface<mixed> $response
     */
    public function mapErrorResponse(ResponseInterface $response): ApiError
    {
        $data = $response->toArray();

        return new ApiError($data['code'], $data['message']);
    }
}

Multi-Host Strategies

Failover

Tries hosts in order, switches on 5xx or network error:

'services' => [
    'api' => [
        'hosts' => [
            'strategy' => 'failover',
            'list' => [
                ['url' => 'https://primary.example.com'],
                ['url' => 'https://backup.example.com'],
            ],
        ],
    ],
],

Round-Robin

Distributes load across hosts:

'services' => [
    'api' => [
        'hosts' => [
            'strategy' => 'round_robin',
            'list' => [
                ['url' => 'https://node1.example.com'],
                ['url' => 'https://node2.example.com'],
                ['url' => 'https://node3.example.com'],
            ],
        ],
    ],
],

Parallel

Sends to all hosts, returns first successful response:

'services' => [
    'api' => [
        'hosts' => [
            'strategy' => 'parallel',
            'list' => [
                ['url' => 'https://fast.example.com'],
                ['url' => 'https://slow.example.com'],
            ],
        ],
    ],
],

Retry Configuration

'defaults' => [
    'retry' => [
        'enabled' => true,
        'max_attempts' => 3,           // Total attempts (1 initial + 2 retries)
        'delay' => 1000,               // Initial delay in ms
        'multiplier' => 2.0,           // Exponential multiplier
        'max_delay' => 30000,          // Maximum delay in ms
        'jitter' => 0.1,               // Random jitter (0-1)
        'retry_on' => [429, 502, 503, 504],
        'retry_on_methods' => ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS'],
        'respect_retry_after' => true,
    ],
],

Authentication Types

Bearer Token

'services' => [
    'api' => [
        'host' => '%env(resolve:API_HOST)%',
        'auth' => [
            'type' => 'bearer',
            'credentials' => [
                'token' => '%env(resolve:API_TOKEN)%',
            ],
        ],
    ],
],

API Key

'services' => [
    'api' => [
        'host' => '%env(resolve:API_HOST)%',
        'auth' => [
            'type' => 'api_key',
            'credentials' => [
                'header' => 'X-Api-Key',  // Default: Api-Key
                'token' => '%env(resolve:API_KEY)%',
            ],
        ],
    ],
],

Basic Auth

'services' => [
    'api' => [
        'host' => '%env(resolve:API_HOST)%',
        'auth' => [
            'type' => 'basic',
            'credentials' => [
                'username' => '%env(resolve:API_USER)%',
                'password' => '%env(resolve:API_PASS)%',
            ],
        ],
    ],
],

Middleware Pipeline

Requests pass through middleware chain in priority order:

Request → Auth(100) → Retry(90) → Logging(80) → CircuitBreaker(60) → MultiHost(10) → Transport
Priority Middleware Purpose
100 AuthMiddleware Adds Authorization header
90 RetryMiddleware Retries with exponential backoff
80 LoggingMiddleware Logs requests/responses
60 CircuitBreakerMiddleware Cascade failure protection
10 MultiHostMiddleware Failover/round-robin/parallel

More details: docs/en/middleware.md

Documentation

License

BSD-3-Clause

deeep/service-client-bundle 适用场景与选型建议

deeep/service-client-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 497 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 02 月 10 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 deeep/service-client-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 deeep/service-client-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 deeep/service-client-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 497
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: BSD-3-Clause
  • 更新时间: 2026-02-10