flikson/laravel-service-client 问题修复 & 功能扩展

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

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

flikson/laravel-service-client

最新稳定版本:1.0.1

Composer 安装命令:

composer require flikson/laravel-service-client

包简介

HTTP client package for Laravel with retry, multi-host, circuit breaker, and async support

README 文档

README

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

Laravel-focused 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+
  • Guzzle 7.9+
  • Illuminate Support/Contracts 11+ | 12+

Installation

composer require flikson/laravel-service-client

Laravel

Laravel package discovery is supported via extra.laravel.providers.

Publish config:

php artisan vendor:publish --tag=deeep-service-client-config

The published config file path:

config/deeep_service_client.php

If package discovery is disabled, register provider manually in config/app.php:

'providers' => [
    // ...
    Deeep\ServiceClient\Laravel\ServiceClientServiceProvider::class,
],

Resolve client from container:

use Deeep\ServiceClient\Contract\HttpClientInterface;

$client = app(HttpClientInterface::class);

Configuration

<?php
// config/deeep_service_client.php

return [
    '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('MY_API_HOST'),
            'auth' => [
                'type' => 'bearer',
                'credentials' => [
                    'token' => env('MY_API_TOKEN'),
                ],
            ],
        ],
        'resilient_api' => [
            'hosts' => [
                'strategy' => 'failover',
                'list' => [
                    ['url' => env('API_PRIMARY')],
                    ['url' => env('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);
$httpClient = app(HttpClient::class);
$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();

$httpClient = app(HttpClient::class);
$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

统计信息

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

GitHub 信息

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

其他信息

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

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固