{"id":19850086,"url":"https://github.com/farzai/transport-php","last_synced_at":"2025-11-24T17:07:46.062Z","repository":{"id":170558532,"uuid":"646656752","full_name":"farzai/transport-php","owner":"farzai","description":"Transport classes and utilities shared among PHP Farzai libraries","archived":false,"fork":false,"pushed_at":"2025-10-24T00:35:48.000Z","size":81,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-24T02:47:18.180Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/farzai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"parsilver"}},"created_at":"2023-05-29T03:30:50.000Z","updated_at":"2025-10-24T00:35:44.000Z","dependencies_parsed_at":"2023-11-29T14:29:51.595Z","dependency_job_id":"0d3b386d-490c-4598-86fc-8cb3edc3bcdd","html_url":"https://github.com/farzai/transport-php","commit_stats":null,"previous_names":["farzai/transport-php"],"tags_count":8,"template":false,"template_full_name":"farzai/package-skeleton-php","purl":"pkg:github/farzai/transport-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/farzai%2Ftransport-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/farzai%2Ftransport-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/farzai%2Ftransport-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/farzai%2Ftransport-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/farzai","download_url":"https://codeload.github.com/farzai/transport-php/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/farzai%2Ftransport-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286079811,"owners_count":27282121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-24T02:00:07.096Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T13:24:17.195Z","updated_at":"2025-11-24T17:07:46.056Z","avatar_url":"https://github.com/farzai.png","language":"PHP","funding_links":["https://github.com/sponsors/parsilver"],"categories":[],"sub_categories":[],"readme":"# Transport PHP\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/farzai/transport.svg?style=flat-square)](https://packagist.org/packages/farzai/transport)\n[![Tests](https://img.shields.io/github/actions/workflow/status/farzai/transport-php/run-tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/farzai/transport-php/actions/workflows/run-tests.yml)\n[![codecov](https://codecov.io/gh/farzai/transport-php/branch/main/graph/badge.svg)](https://codecov.io/gh/farzai/transport-php)\n[![Total Downloads](https://img.shields.io/packagist/dt/farzai/transport.svg?style=flat-square)](https://packagist.org/packages/farzai/transport)\n\nA modern, PSR-compliant HTTP client for PHP with middleware architecture, advanced retry strategies, and fluent API for building requests.\n\n## Features\n\n- ✅ **PSR Standards** - Built on PSR-7 (HTTP Messages), PSR-17 (HTTP Factories), PSR-18 (HTTP Client)\n- ✅ **No Hard Dependencies** - Use any PSR-18 HTTP client (Guzzle, Symfony, or custom)\n- ✅ **Auto-Detection** - Automatically discovers and uses available HTTP clients\n- ✅ **Middleware Architecture** - Extensible plugin system for custom behavior\n- ✅ **Advanced Retry Strategies** - Exponential backoff with jitter, custom retry conditions\n- ✅ **Fluent Request Builder** - Chainable API for building requests\n- ✅ **Immutable Configuration** - Thread-safe, predictable behavior\n- ✅ **Type-Safe** - Full PHP 8.1+ type hints and strict types\n- ✅ **JSON Helpers** - Parse JSON with proper error handling and dot-notation access\n- ✅ **Custom Exceptions** - Detailed error context implementing PSR standards\n- ✅ **Easy to Swap HTTP Clients** - Switch between Guzzle, Symfony, or any PSR-18 client\n- ✅ **File Upload \u0026 Multipart** - RFC 7578 compliant multipart/form-data with file upload support\n- ✅ **Cookie Management** - RFC 6265 compliant automatic cookie handling with session support\n\n## Requirements\n\n- PHP 8.1 or higher\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require farzai/transport\n```\n\nThe library will auto-detect any available PSR-18 HTTP client. If you don't have one installed, we recommend:\n\n```bash\n# Recommended: Modern HTTP client with async support\ncomposer require symfony/http-client\n\n# Alternative: Popular and widely-used\ncomposer require guzzlehttp/guzzle\n```\n\n## Quick Start\n\nTransport PHP automatically detects available HTTP clients (Symfony, Guzzle, etc.) - no configuration needed!\n\n```php\nuse Farzai\\Transport\\TransportBuilder;\n\n// Just works! Auto-detects your HTTP client\n$transport = TransportBuilder::make()\n    -\u003ewithBaseUri('https://api.example.com')\n    -\u003ebuild();\n\n$response = $transport-\u003eget('/users')-\u003esend();\necho $response-\u003ejson('data.0.name'); // Dot notation support!\n```\n\n## Usage\n\n### Basic Usage (Fluent API)\n\n```php\nuse Farzai\\Transport\\TransportBuilder;\n\n// Create a transport client with configuration\n$transport = TransportBuilder::make()\n    -\u003ewithBaseUri('https://api.example.com')\n    -\u003ewithHeaders([\n        'Authorization' =\u003e 'Bearer token123',\n        'Accept' =\u003e 'application/json',\n    ])\n    -\u003ewithTimeout(30)\n    -\u003ebuild();\n\n// Make requests using fluent API\n$response = $transport-\u003eget('/users/123')-\u003esend();\n\n// Access response data\necho $response-\u003estatusCode(); // 200\necho $response-\u003ebody(); // Raw response body\n$data = $response-\u003ejson(); // Parsed JSON as array\n```\n\n### Fluent Request Building\n\n```php\n// GET request with query parameters\n$response = $transport\n    -\u003eget('/users')\n    -\u003ewithQuery(['page' =\u003e 1, 'limit' =\u003e 10])\n    -\u003ewithHeader('X-Custom-Header', 'value')\n    -\u003esend();\n\n// POST with JSON body\n$response = $transport\n    -\u003epost('/users')\n    -\u003ewithJson([\n        'name' =\u003e 'John Doe',\n        'email' =\u003e 'john@example.com'\n    ])\n    -\u003esend();\n\n// POST with form data\n$response = $transport\n    -\u003epost('/login')\n    -\u003ewithForm([\n        'username' =\u003e 'john',\n        'password' =\u003e 'secret'\n    ])\n    -\u003esend();\n\n// With authentication\n$response = $transport\n    -\u003eget('/protected')\n    -\u003ewithBearerToken('your-token')\n    -\u003esend();\n\n// Or basic auth\n$response = $transport\n    -\u003eget('/protected')\n    -\u003ewithBasicAuth('username', 'password')\n    -\u003esend();\n```\n\n### Working with JSON Responses\n\n```php\n// Parse JSON with automatic error handling\n$data = $response-\u003ejson(); // ['id' =\u003e 123, 'name' =\u003e 'John Doe']\n\n// Get specific field using dot notation\n$name = $response-\u003ejson('name'); // 'John Doe'\n$city = $response-\u003ejson('user.address.city'); // 'New York'\n\n// Get as array\n$array = $response-\u003etoArray();\n\n// Safe JSON parsing (returns null on error instead of throwing)\n$data = $response-\u003ejsonOrNull();\n\n// Check if response is successful\nif ($response-\u003eisSuccessful()) {\n    // Handle success (2xx status codes)\n}\n```\n\n### Advanced Retry Logic\n\n```php\nuse Farzai\\Transport\\Retry\\ExponentialBackoffStrategy;\nuse Farzai\\Transport\\Retry\\RetryCondition;\nuse Farzai\\Transport\\Exceptions\\NetworkException;\n\n// Configure retry with exponential backoff\n$transport = TransportBuilder::make()\n    -\u003ewithRetries(\n        maxRetries: 3,\n        strategy: new ExponentialBackoffStrategy(\n            baseDelayMs: 1000,      // Start with 1 second\n            multiplier: 2.0,         // Double each retry\n            maxDelayMs: 30000,       // Cap at 30 seconds\n            useJitter: true          // Add randomization\n        ),\n        condition: (new RetryCondition())\n            -\u003eonExceptions([NetworkException::class])\n    )\n    -\u003ebuild();\n\n// Retries automatically with exponential backoff + jitter\n$response = $transport-\u003eget('/unreliable-endpoint')-\u003esend();\n\n// Or use default retry condition (retries on any exception)\n$transport = TransportBuilder::make()\n    -\u003ewithRetries(\n        maxRetries: 3,\n        condition: RetryCondition::default() // Retries on ANY exception\n    )\n    -\u003ebuild();\n```\n\n**Retry Conditions:**\n- `RetryCondition::default()` - Retries on any exception\n- `(new RetryCondition())-\u003eonExceptions([...])` - Retry only specific exceptions\n- `(new RetryCondition())-\u003eonStatusCodes([500, 502, 503])` - Retry specific HTTP status codes\n\n### Custom Middleware\n\n```php\nuse Farzai\\Transport\\Middleware\\MiddlewareInterface;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\n// Create custom middleware\nclass AuthMiddleware implements MiddlewareInterface\n{\n    public function handle(RequestInterface $request, callable $next): ResponseInterface\n    {\n        // Add auth token to all requests\n        $request = $request-\u003ewithHeader('Authorization', 'Bearer ' . $this-\u003egetToken());\n\n        return $next($request);\n    }\n\n    private function getToken(): string\n    {\n        // Your token logic\n        return 'your-token';\n    }\n}\n\n// Add to transport\n$transport = TransportBuilder::make()\n    -\u003ewithMiddleware(new AuthMiddleware())\n    -\u003ebuild();\n```\n\n### Configuration Options\n\n```php\n$transport = TransportBuilder::make()\n    -\u003ewithBaseUri('https://api.example.com')\n    -\u003ewithHeaders(['Accept' =\u003e 'application/json'])\n    -\u003ewithTimeout(30)  // Seconds\n    -\u003ewithRetries(3)   // Max retry attempts\n    -\u003ewithMiddleware($customMiddleware)\n    -\u003ewithoutDefaultMiddlewares()  // Disable logging, timeout, retry middlewares\n    -\u003esetClient($customPsrClient)  // Use custom PSR-18 client\n    -\u003esetLogger($customLogger)     // Use custom PSR-3 logger\n    -\u003ebuild();\n```\n\n### HTTP Client Selection\n\n```php\nuse Farzai\\Transport\\Factory\\ClientFactory;\n\n// Auto-detect (recommended - uses Symfony \u003e Guzzle \u003e Others)\n$transport = TransportBuilder::make()-\u003ebuild();\n\n// Explicitly use Guzzle\n$transport = TransportBuilder::make()\n    -\u003esetClient(ClientFactory::createGuzzle(['timeout' =\u003e 30]))\n    -\u003ebuild();\n\n// Explicitly use Symfony HTTP Client\n$transport = TransportBuilder::make()\n    -\u003esetClient(ClientFactory::createSymfony(['max_redirects' =\u003e 5]))\n    -\u003ebuild();\n\n// Check which client is being used\necho ClientFactory::getDetectedClientName(); // e.g., \"Symfony\\Component\\HttpClient\\Psr18Client\"\n```\n\n### File Upload \u0026 Multipart Requests\n\n```php\n// Simple file upload\n$response = $transport-\u003epost('/upload')\n    -\u003ewithFile(\n        name: 'document',\n        path: '/path/to/file.pdf',\n        filename: 'report.pdf',\n        additionalFields: ['title' =\u003e 'Monthly Report']\n    )\n    -\u003esend();\n\n// Multiple files with form data\n$response = $transport-\u003epost('/upload')\n    -\u003ewithMultipart([\n        // Text fields\n        ['name' =\u003e 'title', 'contents' =\u003e 'My Upload'],\n        ['name' =\u003e 'description', 'contents' =\u003e 'File description'],\n\n        // File uploads\n        [\n            'name' =\u003e 'avatar',\n            'contents' =\u003e file_get_contents('photo.jpg'),\n            'filename' =\u003e 'avatar.jpg',\n            'content-type' =\u003e 'image/jpeg'\n        ],\n        [\n            'name' =\u003e 'document',\n            'contents' =\u003e fopen('/path/to/file.pdf', 'r'),\n            'filename' =\u003e 'document.pdf'\n        ]\n    ])\n    -\u003esend();\n\n// Advanced: Using MultipartStreamBuilder\nuse Farzai\\Transport\\Multipart\\MultipartStreamBuilder;\n\n$builder = new MultipartStreamBuilder();\n$builder-\u003eaddField('username', 'john_doe')\n    -\u003eaddFile('avatar', '/path/to/avatar.jpg', 'profile.jpg')\n    -\u003eaddFileContents('data', $jsonData, 'data.json', 'application/json');\n\n$response = $transport-\u003epost('/api/upload')\n    -\u003ewithMultipartBuilder($builder)\n    -\u003esend();\n\n// Memory-efficient streaming for large files\nuse Farzai\\Transport\\Multipart\\StreamingMultipartBuilder;\n\n$streamBuilder = new StreamingMultipartBuilder();\n$stream = $streamBuilder\n    -\u003eaddFile('video', '/path/to/large-video.mp4', 'video.mp4')\n    -\u003eaddField('title', 'My Video')\n    -\u003ebuild();\n\n// Streams file without loading entire content into memory\n$response = $transport-\u003erequest()\n    -\u003ewithBody($stream)\n    -\u003ewithHeader('Content-Type', $streamBuilder-\u003egetContentType())\n    -\u003epost('/upload')\n    -\u003esend();\n```\n\n**Note:** The library automatically selects `StreamingMultipartBuilder` for large files (\u003e1MB by default) to optimize memory usage. You can also manually use it for memory-efficient uploads of any size.\n\n### Cookie Management\n\n```php\n// Automatic cookie handling\n$transport = TransportBuilder::make()\n    -\u003ewithBaseUri('https://api.example.com')\n    -\u003ewithCookies() // Enable automatic cookie management\n    -\u003ebuild();\n\n// Login - cookies are automatically stored\n$transport-\u003epost('/login')\n    -\u003ewithJson(['username' =\u003e 'user', 'password' =\u003e 'pass'])\n    -\u003esend();\n\n// Subsequent requests automatically include cookies\n$response = $transport-\u003eget('/profile')-\u003esend();\n\n// Advanced: Manual cookie management\nuse Farzai\\Transport\\Cookie\\CookieJar;\nuse Farzai\\Transport\\Cookie\\Cookie;\n\n$cookieJar = new CookieJar();\n\n// Add cookies manually\n$cookieJar-\u003esetCookie(new Cookie(\n    name: 'session_id',\n    value: 'abc123',\n    expiresAt: time() + 3600,\n    domain: 'example.com',\n    path: '/',\n    secure: true,\n    httpOnly: true\n));\n\n$transport = TransportBuilder::make()\n    -\u003ewithCookieJar($cookieJar)\n    -\u003ebuild();\n\n// Inspect cookies\necho \"Cookies: {$cookieJar-\u003ecount()}\\n\";\nforeach ($cookieJar-\u003egetAllCookies() as $cookie) {\n    echo \"{$cookie-\u003egetName()}: {$cookie-\u003egetValue()}\\n\";\n}\n\n// Export/Import cookies for persistence\n$data = $cookieJar-\u003etoArray();\nfile_put_contents('cookies.json', json_encode($data));\n\n// Later...\n$newJar = new CookieJar();\n$newJar-\u003efromArray(json_decode(file_get_contents('cookies.json'), true));\n```\n\n**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.\n\n### Event Monitoring\n\nMonitor HTTP requests lifecycle with event listeners:\n\n```php\nuse Farzai\\Transport\\Events\\RequestSendingEvent;\nuse Farzai\\Transport\\Events\\ResponseReceivedEvent;\nuse Farzai\\Transport\\Events\\RequestFailedEvent;\nuse Farzai\\Transport\\Events\\RetryAttemptEvent;\n\n$transport = TransportBuilder::make()\n    -\u003ewithBaseUri('https://api.example.com')\n    // Track successful responses\n    -\u003eaddEventListener(ResponseReceivedEvent::class, function ($event) {\n        printf(\n            \"[SUCCESS] %s %s → %d (%.2fms)\\n\",\n            $event-\u003egetMethod(),\n            $event-\u003egetUri(),\n            $event-\u003egetStatusCode(),\n            $event-\u003egetDuration()\n        );\n    })\n    // Track failed requests\n    -\u003eaddEventListener(RequestFailedEvent::class, function ($event) {\n        printf(\n            \"[ERROR] %s %s failed: %s\\n\",\n            $event-\u003egetMethod(),\n            $event-\u003egetUri(),\n            $event-\u003egetExceptionMessage()\n        );\n    })\n    // Monitor retry attempts\n    -\u003eaddEventListener(RetryAttemptEvent::class, function ($event) {\n        printf(\n            \"[RETRY] Attempt %d/%d (delay: %dms)\\n\",\n            $event-\u003egetAttemptNumber(),\n            $event-\u003egetMaxAttempts(),\n            $event-\u003egetDelay()\n        );\n    })\n    -\u003ewithRetries(3)\n    -\u003ebuild();\n\n// Events are automatically dispatched during request lifecycle\n$response = $transport-\u003eget('/api/endpoint')-\u003esend();\n```\n\n**Available Events:**\n- `RequestSendingEvent` - Before a request is sent\n- `ResponseReceivedEvent` - After successful response (includes duration metrics)\n- `RequestFailedEvent` - When a request fails with exception details\n- `RetryAttemptEvent` - Before each retry attempt with delay information\n\n**Use Cases:**\n- Performance monitoring and metrics collection\n- Logging and debugging request/response cycles\n- Custom retry notifications\n- Request/response instrumentation\n\n### Error Handling\n\n```php\nuse Farzai\\Transport\\Exceptions\\ClientException;\nuse Farzai\\Transport\\Exceptions\\ServerException;\nuse Farzai\\Transport\\Exceptions\\RetryExhaustedException;\nuse Farzai\\Transport\\Exceptions\\JsonParseException;\n\n// Throw exception on non-2xx responses\ntry {\n    $response-\u003ethrow();\n} catch (ClientException $e) {\n    // 4xx errors\n    echo \"Client error: {$e-\u003egetStatusCode()}\\n\";\n    echo \"Request: {$e-\u003egetRequest()-\u003egetUri()}\\n\";\n    var_dump($e-\u003egetContext()); // Rich debugging context\n} catch (ServerException $e) {\n    // 5xx errors\n    echo \"Server error: {$e-\u003egetStatusCode()}\\n\";\n}\n\n// Custom error handling callback\n$response-\u003ethrow(function ($response, $exception) {\n    if ($response-\u003estatusCode() === 404) {\n        throw new \\Exception('Resource not found!');\n    }\n    throw $exception;\n});\n\n// Handle retry exhaustion\ntry {\n    $response = $transport-\u003eget('/flaky-endpoint')-\u003esend();\n} catch (RetryExhaustedException $e) {\n    echo \"Failed after {$e-\u003egetAttempts()} attempts\\n\";\n    echo \"Delays used: \" . implode(', ', $e-\u003egetDelaysUsed()) . \"ms\\n\";\n\n    foreach ($e-\u003egetRetryExceptions() as $attempt =\u003e $exception) {\n        echo \"Attempt $attempt: {$exception-\u003egetMessage()}\\n\";\n    }\n}\n\n// Handle JSON parse errors\ntry {\n    $data = $response-\u003ejson();\n} catch (JsonParseException $e) {\n    echo \"Invalid JSON: {$e-\u003egetMessage()}\\n\";\n    echo \"JSON string: {$e-\u003ejsonString}\\n\";\n    echo \"Error code: {$e-\u003ejsonErrorCode}\\n\";\n}\n```\n\n### Using Custom PSR-18 Client and Logger\n\n```php\nuse Farzai\\Transport\\TransportBuilder;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\n\n// Create custom logger\n$logger = new Logger('http-client');\n$logger-\u003epushHandler(new StreamHandler('path/to/your.log'));\n\n// Use any PSR-18 compliant client\n$client = new \\Your\\Custom\\Psr18Client();\n\n$transport = TransportBuilder::make()\n    -\u003esetClient($client)\n    -\u003esetLogger($logger)\n    -\u003ebuild();\n```\n\n### Testing with Response Builder\n\n```php\nuse Farzai\\Transport\\ResponseBuilder;\n\n$response = ResponseBuilder::create()\n    -\u003estatusCode(200)\n    -\u003ewithHeader('Content-Type', 'application/json')\n    -\u003ewithBody('{\"success\": true}')\n    -\u003ebuild();\n\n// Or use the fluent builder methods\n$response = ResponseBuilder::create()\n    -\u003estatusCode(404)\n    -\u003ewithHeaders([\n        'Content-Type' =\u003e 'application/json',\n        'X-Request-ID' =\u003e '12345'\n    ])\n    -\u003ewithBody('{\"error\": \"Not found\"}')\n    -\u003ewithVersion('1.1')\n    -\u003ewithReason('Not Found')\n    -\u003ebuild();\n```\n\n## Documentation\n\n- **[Examples](examples/)** - Practical usage examples:\n  - [Basic Usage](examples/basic-usage.php)\n  - [Custom HTTP Clients](examples/custom-client.php)\n  - [Advanced Retry Logic](examples/advanced-retry.php)\n  - [Custom Middleware](examples/middleware-example.php)\n  - [File Upload](examples/file-upload.php)\n  - [Streaming Upload](examples/streaming-upload.php)\n  - [Cookie Session Management](examples/cookie-session.php)\n  - [Event Monitoring](examples/event-monitoring.php)\n\n## Architecture\n\n### No Hard Dependencies on Guzzle\n\nTransport PHP v2.x uses PSR standards and auto-detection:\n\n- **PSR-7** - HTTP Message Interface\n- **PSR-17** - HTTP Factories for creating requests/responses\n- **PSR-18** - HTTP Client Interface\n\nThis means you can use **any** PSR-18 compliant HTTP client:\n- ✅ Symfony HTTP Client (modern, async, HTTP/2)\n- ✅ Guzzle (popular, stable)\n- ✅ Any custom PSR-18 implementation\n\n### Middleware System\n\n```\nRequest → Middleware Stack → HTTP Client → Response\n          ↓\n     [LoggingMiddleware]\n     [TimeoutMiddleware]\n     [RetryMiddleware]\n     [CustomMiddleware...]\n```\n\nDefault middlewares (can be disabled with `withoutDefaultMiddlewares()`):\n- **LoggingMiddleware**: Logs requests and responses\n- **TimeoutMiddleware**: Enforces request timeouts\n- **RetryMiddleware**: Handles retry logic with configurable strategies\n\n### Immutable Configuration\n\nAll configuration is immutable and set during the build phase:\n\n```php\n// ✅ Correct - configuration during build\n$transport = TransportBuilder::make()\n    -\u003ewithTimeout(30)\n    -\u003ewithRetries(3)\n    -\u003ebuild();\n```\n\nThis makes the Transport instance:\n- **Thread-safe** - Can be safely shared across threads\n- **Predictable** - Configuration can't change unexpectedly\n- **Easier to test** - No hidden state changes\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Code Quality\n\n```bash\n# Run tests with coverage\ncomposer test-coverage\n\n# Fix code style\ncomposer format\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](docs/contributing/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [parsilver](https://github.com/parsilver)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffarzai%2Ftransport-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffarzai%2Ftransport-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffarzai%2Ftransport-php/lists"}