定制 farzai/transport 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

farzai/transport

Composer 安装命令:

composer require farzai/transport

包简介

A modern, PSR-compliant HTTP client for PHP with middleware architecture, advanced retry strategies, and fluent API for building requests

README 文档

README

Latest Version on Packagist Tests codecov Total Downloads

A modern, PSR-compliant HTTP client for PHP with middleware architecture, advanced retry strategies, and fluent API for building requests.

Features

  • PSR Standards - Built on PSR-7 (HTTP Messages), PSR-17 (HTTP Factories), PSR-18 (HTTP Client)
  • No Hard Dependencies - Use any PSR-18 HTTP client (Guzzle, Symfony, or custom)
  • Auto-Detection - Automatically discovers and uses available HTTP clients
  • Middleware Architecture - Extensible plugin system for custom behavior
  • Advanced Retry Strategies - Exponential backoff with jitter, custom retry conditions
  • Fluent Request Builder - Chainable API for building requests
  • Immutable Configuration - Thread-safe, predictable behavior
  • Type-Safe - Full PHP 8.1+ type hints and strict types
  • JSON Helpers - Parse JSON with proper error handling and dot-notation access
  • Custom Exceptions - Detailed error context implementing PSR standards
  • Easy to Swap HTTP Clients - Switch between Guzzle, Symfony, or any PSR-18 client
  • File Upload & Multipart - RFC 7578 compliant multipart/form-data with file upload support
  • Cookie Management - RFC 6265 compliant automatic cookie handling with session support

Requirements

  • PHP 8.1 or higher

Installation

You can install the package via composer:

composer require farzai/transport

The library will auto-detect any available PSR-18 HTTP client. If you don't have one installed, we recommend:

# Recommended: Modern HTTP client with async support
composer require symfony/http-client

# Alternative: Popular and widely-used
composer require guzzlehttp/guzzle

Quick Start

Transport PHP automatically detects available HTTP clients (Symfony, Guzzle, etc.) - no configuration needed!

use Farzai\Transport\TransportBuilder;

// Just works! Auto-detects your HTTP client
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->build();

$response = $transport->get('/users')->send();
echo $response->json('data.0.name'); // Dot notation support!

Usage

Basic Usage (Fluent API)

use Farzai\Transport\TransportBuilder;

// Create a transport client with configuration
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withHeaders([
        'Authorization' => 'Bearer token123',
        'Accept' => 'application/json',
    ])
    ->withTimeout(30)
    ->build();

// Make requests using fluent API
$response = $transport->get('/users/123')->send();

// Access response data
echo $response->statusCode(); // 200
echo $response->body(); // Raw response body
$data = $response->json(); // Parsed JSON as array

Fluent Request Building

// GET request with query parameters
$response = $transport
    ->get('/users')
    ->withQuery(['page' => 1, 'limit' => 10])
    ->withHeader('X-Custom-Header', 'value')
    ->send();

// POST with JSON body
$response = $transport
    ->post('/users')
    ->withJson([
        'name' => 'John Doe',
        'email' => 'john@example.com'
    ])
    ->send();

// POST with form data
$response = $transport
    ->post('/login')
    ->withForm([
        'username' => 'john',
        'password' => 'secret'
    ])
    ->send();

// With authentication
$response = $transport
    ->get('/protected')
    ->withBearerToken('your-token')
    ->send();

// Or basic auth
$response = $transport
    ->get('/protected')
    ->withBasicAuth('username', 'password')
    ->send();

Working with JSON Responses

// Parse JSON with automatic error handling
$data = $response->json(); // ['id' => 123, 'name' => 'John Doe']

// Get specific field using dot notation
$name = $response->json('name'); // 'John Doe'
$city = $response->json('user.address.city'); // 'New York'

// Get as array
$array = $response->toArray();

// Safe JSON parsing (returns null on error instead of throwing)
$data = $response->jsonOrNull();

// Check if response is successful
if ($response->isSuccessful()) {
    // Handle success (2xx status codes)
}

Advanced Retry Logic

use Farzai\Transport\Retry\ExponentialBackoffStrategy;
use Farzai\Transport\Retry\RetryCondition;
use Farzai\Transport\Exceptions\NetworkException;

// Configure retry with exponential backoff
$transport = TransportBuilder::make()
    ->withRetries(
        maxRetries: 3,
        strategy: new ExponentialBackoffStrategy(
            baseDelayMs: 1000,      // Start with 1 second
            multiplier: 2.0,         // Double each retry
            maxDelayMs: 30000,       // Cap at 30 seconds
            useJitter: true          // Add randomization
        ),
        condition: (new RetryCondition())
            ->onExceptions([NetworkException::class])
    )
    ->build();

// Retries automatically with exponential backoff + jitter
$response = $transport->get('/unreliable-endpoint')->send();

// Or use default retry condition (retries on any exception)
$transport = TransportBuilder::make()
    ->withRetries(
        maxRetries: 3,
        condition: RetryCondition::default() // Retries on ANY exception
    )
    ->build();

Retry Conditions:

  • RetryCondition::default() - Retries on any exception
  • (new RetryCondition())->onExceptions([...]) - Retry only specific exceptions
  • (new RetryCondition())->onStatusCodes([500, 502, 503]) - Retry specific HTTP status codes

Custom Middleware

use Farzai\Transport\Middleware\MiddlewareInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

// Create custom middleware
class AuthMiddleware implements MiddlewareInterface
{
    public function handle(RequestInterface $request, callable $next): ResponseInterface
    {
        // Add auth token to all requests
        $request = $request->withHeader('Authorization', 'Bearer ' . $this->getToken());

        return $next($request);
    }

    private function getToken(): string
    {
        // Your token logic
        return 'your-token';
    }
}

// Add to transport
$transport = TransportBuilder::make()
    ->withMiddleware(new AuthMiddleware())
    ->build();

Configuration Options

$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withHeaders(['Accept' => 'application/json'])
    ->withTimeout(30)  // Seconds
    ->withRetries(3)   // Max retry attempts
    ->withMiddleware($customMiddleware)
    ->withoutDefaultMiddlewares()  // Disable logging, timeout, retry middlewares
    ->setClient($customPsrClient)  // Use custom PSR-18 client
    ->setLogger($customLogger)     // Use custom PSR-3 logger
    ->build();

HTTP Client Selection

use Farzai\Transport\Factory\ClientFactory;

// Auto-detect (recommended - uses Symfony > Guzzle > Others)
$transport = TransportBuilder::make()->build();

// Explicitly use Guzzle
$transport = TransportBuilder::make()
    ->setClient(ClientFactory::createGuzzle(['timeout' => 30]))
    ->build();

// Explicitly use Symfony HTTP Client
$transport = TransportBuilder::make()
    ->setClient(ClientFactory::createSymfony(['max_redirects' => 5]))
    ->build();

// Check which client is being used
echo ClientFactory::getDetectedClientName(); // e.g., "Symfony\Component\HttpClient\Psr18Client"

File Upload & Multipart Requests

// Simple file upload
$response = $transport->post('/upload')
    ->withFile(
        name: 'document',
        path: '/path/to/file.pdf',
        filename: 'report.pdf',
        additionalFields: ['title' => 'Monthly Report']
    )
    ->send();

// Multiple files with form data
$response = $transport->post('/upload')
    ->withMultipart([
        // Text fields
        ['name' => 'title', 'contents' => 'My Upload'],
        ['name' => 'description', 'contents' => 'File description'],

        // File uploads
        [
            'name' => 'avatar',
            'contents' => file_get_contents('photo.jpg'),
            'filename' => 'avatar.jpg',
            'content-type' => 'image/jpeg'
        ],
        [
            'name' => 'document',
            'contents' => fopen('/path/to/file.pdf', 'r'),
            'filename' => 'document.pdf'
        ]
    ])
    ->send();

// Advanced: Using MultipartStreamBuilder
use Farzai\Transport\Multipart\MultipartStreamBuilder;

$builder = new MultipartStreamBuilder();
$builder->addField('username', 'john_doe')
    ->addFile('avatar', '/path/to/avatar.jpg', 'profile.jpg')
    ->addFileContents('data', $jsonData, 'data.json', 'application/json');

$response = $transport->post('/api/upload')
    ->withMultipartBuilder($builder)
    ->send();

// Memory-efficient streaming for large files
use Farzai\Transport\Multipart\StreamingMultipartBuilder;

$streamBuilder = new StreamingMultipartBuilder();
$stream = $streamBuilder
    ->addFile('video', '/path/to/large-video.mp4', 'video.mp4')
    ->addField('title', 'My Video')
    ->build();

// Streams file without loading entire content into memory
$response = $transport->request()
    ->withBody($stream)
    ->withHeader('Content-Type', $streamBuilder->getContentType())
    ->post('/upload')
    ->send();

Note: The library automatically selects StreamingMultipartBuilder for large files (>1MB by default) to optimize memory usage. You can also manually use it for memory-efficient uploads of any size.

Cookie Management

// Automatic cookie handling
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withCookies() // Enable automatic cookie management
    ->build();

// Login - cookies are automatically stored
$transport->post('/login')
    ->withJson(['username' => 'user', 'password' => 'pass'])
    ->send();

// Subsequent requests automatically include cookies
$response = $transport->get('/profile')->send();

// Advanced: Manual cookie management
use Farzai\Transport\Cookie\CookieJar;
use Farzai\Transport\Cookie\Cookie;

$cookieJar = new CookieJar();

// Add cookies manually
$cookieJar->setCookie(new Cookie(
    name: 'session_id',
    value: 'abc123',
    expiresAt: time() + 3600,
    domain: 'example.com',
    path: '/',
    secure: true,
    httpOnly: true
));

$transport = TransportBuilder::make()
    ->withCookieJar($cookieJar)
    ->build();

// Inspect cookies
echo "Cookies: {$cookieJar->count()}\n";
foreach ($cookieJar->getAllCookies() as $cookie) {
    echo "{$cookie->getName()}: {$cookie->getValue()}\n";
}

// Export/Import cookies for persistence
$data = $cookieJar->toArray();
file_put_contents('cookies.json', json_encode($data));

// Later...
$newJar = new CookieJar();
$newJar->fromArray(json_decode(file_get_contents('cookies.json'), true));

Performance Note: The cookie management system automatically optimizes for different workloads. For applications with many cookies (50+), it uses indexed collections with O(1) domain lookups. For smaller cookie counts, it uses simpler collections to minimize overhead.

Event Monitoring

Monitor HTTP requests lifecycle with event listeners:

use Farzai\Transport\Events\RequestSendingEvent;
use Farzai\Transport\Events\ResponseReceivedEvent;
use Farzai\Transport\Events\RequestFailedEvent;
use Farzai\Transport\Events\RetryAttemptEvent;

$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    // Track successful responses
    ->addEventListener(ResponseReceivedEvent::class, function ($event) {
        printf(
            "[SUCCESS] %s %s → %d (%.2fms)\n",
            $event->getMethod(),
            $event->getUri(),
            $event->getStatusCode(),
            $event->getDuration()
        );
    })
    // Track failed requests
    ->addEventListener(RequestFailedEvent::class, function ($event) {
        printf(
            "[ERROR] %s %s failed: %s\n",
            $event->getMethod(),
            $event->getUri(),
            $event->getExceptionMessage()
        );
    })
    // Monitor retry attempts
    ->addEventListener(RetryAttemptEvent::class, function ($event) {
        printf(
            "[RETRY] Attempt %d/%d (delay: %dms)\n",
            $event->getAttemptNumber(),
            $event->getMaxAttempts(),
            $event->getDelay()
        );
    })
    ->withRetries(3)
    ->build();

// Events are automatically dispatched during request lifecycle
$response = $transport->get('/api/endpoint')->send();

Available Events:

  • RequestSendingEvent - Before a request is sent
  • ResponseReceivedEvent - After successful response (includes duration metrics)
  • RequestFailedEvent - When a request fails with exception details
  • RetryAttemptEvent - Before each retry attempt with delay information

Use Cases:

  • Performance monitoring and metrics collection
  • Logging and debugging request/response cycles
  • Custom retry notifications
  • Request/response instrumentation

Error Handling

use Farzai\Transport\Exceptions\ClientException;
use Farzai\Transport\Exceptions\ServerException;
use Farzai\Transport\Exceptions\RetryExhaustedException;
use Farzai\Transport\Exceptions\JsonParseException;

// Throw exception on non-2xx responses
try {
    $response->throw();
} catch (ClientException $e) {
    // 4xx errors
    echo "Client error: {$e->getStatusCode()}\n";
    echo "Request: {$e->getRequest()->getUri()}\n";
    var_dump($e->getContext()); // Rich debugging context
} catch (ServerException $e) {
    // 5xx errors
    echo "Server error: {$e->getStatusCode()}\n";
}

// Custom error handling callback
$response->throw(function ($response, $exception) {
    if ($response->statusCode() === 404) {
        throw new \Exception('Resource not found!');
    }
    throw $exception;
});

// Handle retry exhaustion
try {
    $response = $transport->get('/flaky-endpoint')->send();
} catch (RetryExhaustedException $e) {
    echo "Failed after {$e->getAttempts()} attempts\n";
    echo "Delays used: " . implode(', ', $e->getDelaysUsed()) . "ms\n";

    foreach ($e->getRetryExceptions() as $attempt => $exception) {
        echo "Attempt $attempt: {$exception->getMessage()}\n";
    }
}

// Handle JSON parse errors
try {
    $data = $response->json();
} catch (JsonParseException $e) {
    echo "Invalid JSON: {$e->getMessage()}\n";
    echo "JSON string: {$e->jsonString}\n";
    echo "Error code: {$e->jsonErrorCode}\n";
}

Using Custom PSR-18 Client and Logger

use Farzai\Transport\TransportBuilder;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create custom logger
$logger = new Logger('http-client');
$logger->pushHandler(new StreamHandler('path/to/your.log'));

// Use any PSR-18 compliant client
$client = new \Your\Custom\Psr18Client();

$transport = TransportBuilder::make()
    ->setClient($client)
    ->setLogger($logger)
    ->build();

Testing with Response Builder

use Farzai\Transport\ResponseBuilder;

$response = ResponseBuilder::create()
    ->statusCode(200)
    ->withHeader('Content-Type', 'application/json')
    ->withBody('{"success": true}')
    ->build();

// Or use the fluent builder methods
$response = ResponseBuilder::create()
    ->statusCode(404)
    ->withHeaders([
        'Content-Type' => 'application/json',
        'X-Request-ID' => '12345'
    ])
    ->withBody('{"error": "Not found"}')
    ->withVersion('1.1')
    ->withReason('Not Found')
    ->build();

Documentation

Architecture

No Hard Dependencies on Guzzle

Transport PHP v2.x uses PSR standards and auto-detection:

  • PSR-7 - HTTP Message Interface
  • PSR-17 - HTTP Factories for creating requests/responses
  • PSR-18 - HTTP Client Interface

This means you can use any PSR-18 compliant HTTP client:

  • ✅ Symfony HTTP Client (modern, async, HTTP/2)
  • ✅ Guzzle (popular, stable)
  • ✅ Any custom PSR-18 implementation

Middleware System

Request → Middleware Stack → HTTP Client → Response
          ↓
     [LoggingMiddleware]
     [TimeoutMiddleware]
     [RetryMiddleware]
     [CustomMiddleware...]

Default middlewares (can be disabled with withoutDefaultMiddlewares()):

  • LoggingMiddleware: Logs requests and responses
  • TimeoutMiddleware: Enforces request timeouts
  • RetryMiddleware: Handles retry logic with configurable strategies

Immutable Configuration

All configuration is immutable and set during the build phase:

// ✅ Correct - configuration during build
$transport = TransportBuilder::make()
    ->withTimeout(30)
    ->withRetries(3)
    ->build();

This makes the Transport instance:

  • Thread-safe - Can be safely shared across threads
  • Predictable - Configuration can't change unexpectedly
  • Easier to test - No hidden state changes

Testing

composer test

Code Quality

# Run tests with coverage
composer test-coverage

# Fix code style
composer format

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

farzai/transport 适用场景与选型建议

farzai/transport 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 7.21k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2023 年 06 月 01 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「http」 「middleware」 「retry」 「cookie」 「psr」 「API-Client」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

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

围绕 farzai/transport 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-06-01