PHPackages                             jooservices/client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. jooservices/client

ActiveLibrary[HTTP &amp; Networking](/categories/http)

jooservices/client
==================

Strict, extensible PHP 8.5+ HTTP client wrapper for JOOservices

v2.1.0(2w ago)02.1k1[4 PRs](https://github.com/jooservices/client/pulls)4MITPHPPHP ^8.5CI passing

Since Jan 31Pushed 1w agoCompare

[ Source](https://github.com/jooservices/client)[ Packagist](https://packagist.org/packages/jooservices/client)[ RSS](/packages/jooservices-client/feed)WikiDiscussions develop Synced 1w ago

READMEChangelog (10)Dependencies (58)Versions (22)Used By (4)

JOOservices HTTP Client
=======================

[](#jooservices-http-client)

A robust, layered HTTP client wrapper designed for extensibility, strict typing, and high performance. Built with a clean, package-oriented architecture that decouples transport integration from client behavior.

[![CI](https://github.com/jooservices/client/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/jooservices/client/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/7b5aad8d94338e747ce58ac6900fe0069a3fc891200bb53882e33647fac9d114/68747470733a2f2f636f6465636f762e696f2f67682f6a6f6f73657276696365732f636c69656e742f6272616e63682f646576656c6f702f67726170682f62616467652e737667)](https://codecov.io/gh/jooservices/client)[![OpenSSF Scorecard](https://camo.githubusercontent.com/bd0f839e27845a9b96d30f7abd6f47dc4942cc9d4dfcab561d18e979b911cc47/68747470733a2f2f6170692e736563757269747973636f726563617264732e6465762f70726f6a656374732f6769746875622e636f6d2f6a6f6f73657276696365732f636c69656e742f6261646765)](https://securityscorecards.dev/viewer/?uri=github.com/jooservices/client)[![PHP Version](https://camo.githubusercontent.com/3fe1b9f28029d16b30242054cf5a6fe168f12cee4d294ec395f5bca64b874909/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e352d626c7565)](https://php.net/)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://opensource.org/licenses/MIT)[![Docker](https://camo.githubusercontent.com/5dbf15dd763dd6ad305d9ac2cc07a7b19027e2db5320577c90ff7310942f6e10/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f636b65722d656e61626c65642d3234393645443f6c6f676f3d646f636b6572266c6f676f436f6c6f723d7768697465)](Dockerfile)[![Packagist](https://camo.githubusercontent.com/3e49027eb04a3f1c9311050a310a5074111ae538dadc91da17ed20f937256ae7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6f6f73657276696365732f636c69656e74)](https://packagist.org/packages/jooservices/client)[![Latest Release](https://camo.githubusercontent.com/e685e5221ec9dd62984ce66106fcaacd5bdee08c6a51635e58d24bce44be26a8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f6a6f6f73657276696365732f636c69656e74)](https://github.com/jooservices/client/releases)[![AI Workflow](https://camo.githubusercontent.com/0d90c9468791301baffd6ee14f1bfa69ec670a4059c79a2c0320564b514b4fe8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41492d576f726b666c6f772d696e666f726d6174696f6e616c)](docs/04-development/ai-skills.md)

Features
--------

[](#features)

- **Strictly Typed**: Configuration object (`ClientConfig`) ensures type safety before requests start.
- **Layered Architecture**: Adapters (Guzzle) are isolated from Core Logic.
- **Resilience**: Built-in Retry (Backoff/Jitter), Circuit Breaker, Rate Limit, Bulkhead, Fallback, and Deadline middleware.
- **Observability**: Logging, W3C trace context, metrics, and correlation IDs.
- **Auth**: Bearer, API key, Basic auth, and OAuth token refresh middleware.
- **Performance**: &lt; 10μs overhead per request.
- **Guzzle 7.10 / 8**: Native Guzzle transport with stable package exceptions.
- **Testing**: Builder-native fakes with deterministic retry and rate-limit sleeps.

Installation
------------

[](#installation)

```
composer require jooservices/client
```

Quick Start
-----------

[](#quick-start)

### Basic Usage

[](#basic-usage)

Use the **ClientBuilder** to create an instance.

```
use JOOservices\Client\Client\ClientBuilder;

$client = ClientBuilder::create()
    ->withBaseUri('https://api.example.com')
    ->withTimeout(5)
    ->withHeader('Authorization', 'Bearer token')
    ->build();

$response = $client->get('/users/1');

echo $response->status(); // 200
print_r($response->json()); // ['id' => 1, ...]
```

### Downloading Files

[](#downloading-files)

The client supports memory-efficient file downloads. The response body is written directly to the target destination path using a stream (avoiding buffering the download in memory). Response body logging is automatically skipped for downloads.

```
// Synchronous download
$client->download('https://example.com/largefile.zip', '/path/to/save/largefile.zip');

// Asynchronous download
$promise = $client->downloadAsync('https://example.com/largefile.zip', '/path/to/save/largefile.zip');
$promise->wait();
```

### JSON, Uploads, and Response Helpers

[](#json-uploads-and-response-helpers)

```
$client = ClientBuilder::create()->withJsonDefaults()->withRedirects(true)->build();
$response = $client->postJson('/users', ['name' => 'Ada']);

if ($response->successful()) {
    echo $response->body();
}

$client->upload('/documents', __DIR__ . '/report.pdf', ['category' => 'reports']);
```

`upload()` accepts a readable file path and creates the multipart `file` part. `body()` preserves the current position for seekable streams.

### Testing with fakes

[](#testing-with-fakes)

Production code and tests share the same `ClientBuilder::create()->build()` entry point — call `ClientBuilder::fake()` first and everything else about how the client is built stays the same.

```
use JOOservices\Client\Testing\TestResponse;

ClientBuilder::fake([
    TestResponse::times(2, TestResponse::status(503))->then(TestResponse::ok(['id' => 1])),
]);

$client = ClientBuilder::create()->withRetry(new RetryConfig())->build();
$response = $client->get('/health');

ClientBuilder::assertSentCount(3);          // 2 failed attempts + 1 success, all recorded
ClientBuilder::assertSent('GET', '/health');
ClientBuilder::clearFake();                 // always call in tearDown(), or use InteractsWithHttpClient
```

`TestResponse` covers every outcome the real transport can produce: `ok()`, `json()`, `status()`, `notFound()`, `serverError()`, `timeout()`, `connectionError()`, `fatal()`, and `asHttpError()` for forcing an `HttpResponseException` regardless of the builder's `withHttpErrors()` setting. Retry and rate-limit middleware automatically use a `NullSleeper` while faked, so retried requests don't actually sleep. Fakes cannot be combined with a custom adapter or Guzzle `handler` option — `build()`throws `InvalidConfigurationException` instead of silently hitting real network.

Use the `InteractsWithHttpClient` trait to clear the fake automatically after every test:

```
use JOOservices\Client\Testing\InteractsWithHttpClient;

final class BillingClientTest extends TestCase
{
    use InteractsWithHttpClient; // calls ClientBuilder::clearFake() in tearDown()
}
```

Async calls retain the documented synchronous-middleware limitation.

### Async Requests &amp; Batching

[](#async-requests--batching)

```
// Single Async Request
$promise = $client->getAsync('/users/1');
$response = $promise->wait();

// Batch Processing (Concurrent)
$results = $client->batch([
    'user1' => fn() => $client->getAsync('/users/1'),
    'user2' => fn() => $client->getAsync('/users/2'),
]);

print_r($results['user1']->json());
```

Advanced Configuration
----------------------

[](#advanced-configuration)

### Resilience (Retry &amp; Circuit Breaker)

[](#resilience-retry--circuit-breaker)

```
use JOOservices\Client\Resilience\RetryConfig;
use JOOservices\Client\Resilience\CircuitBreakerConfig;

$client = ClientBuilder::create()
    ->withRetry(new RetryConfig(
        maxAttempts: 3,
        baseDelayMs: 100
    ))
    ->withCircuitBreaker(new CircuitBreakerConfig(
        failureThreshold: 5,
        recoveryTimeoutMs: 10000
    ))
    ->build();
```

### Production middleware stack

[](#production-middleware-stack)

Register middleware outermost-first. Use individual helpers or the preset:

```
use JOOservices\Client\Client\ClientBuilder;
use JOOservices\Client\Resilience\RateLimitConfig;
use JOOservices\Client\Resilience\RetryConfig;
use JOOservices\Client\Support\InMemoryMetricsRecorder;
use JOOservices\Client\ValueObjects\TraceContextConfig;

$client = ClientBuilder::create()
    ->withBaseUri('https://api.example.com')
    ->withHeader('Accept', 'application/json')
    ->withBearerToken($token)
    ->withProductionMiddlewareOrder(
        rateLimit: new RateLimitConfig(maxTokens: 50, refillRatePerSecond: 50),
        traceContext: new TraceContextConfig(),
        metrics: new InMemoryMetricsRecorder(),
        retry: new RetryConfig(),
    )
    ->build();
```

Per-request options: `idempotency_key`, `deadline_ms`, `rate_limit_bypass`, `cache_bypass`, `cache_ttl`, `partition_key`, `fallback_enabled`.

### Logging &amp; Caching

[](#logging--caching)

```
use JOOservices\Client\Logging\MonologFactory;
use JOOservices\Client\Cache\FilesystemCache;
use JOOservices\Client\Support\CachedExternalWanIpProvider;

$logger = MonologFactory::createDaily('my-app', __DIR__ . '/logs');
$cache = new FilesystemCache(__DIR__ . '/cache');

$client = ClientBuilder::create()
    ->withLogger($logger, logBodies: true)
    ->withWanIpProvider(new CachedExternalWanIpProvider()) // opt-in WAN IP in logs
    ->withCache($cache, defaultTtl: 3600)
    ->build();
```

Request and response body logging should stay opt-in. Keep `logBodies: false` unless the integration explicitly needs body-level diagnostics and the payload is safe to record.

WAN/public IP enrichment is also opt-in. It may be personal or infrastructure-sensitive data; enable it only where collection, retention, and access to the resulting log context are appropriate for your deployment.

### Mongo logging

[](#mongo-logging)

```
use JOOservices\Client\Logging\MongoDbLogConfig;

// Shared MongoDB\Collection (DI)
$client = ClientBuilder::create()->withMongoCollectionLogging($collection)->build();

// URI + database (scripts / small apps)
$client = ClientBuilder::create()
    ->withMongoUriLogging(getenv('MONGODB_URI'), 'my_app', 'http_client_logs')
    ->build();

// Fully-specified config (bounded timeouts, byte limits, schema version)
$client = ClientBuilder::create()
    ->withMongoLoggingConfig(new MongoDbLogConfig(getenv('MONGODB_URI'), 'my_app'))
    ->build();
```

Each method is unambiguous about its source — there is no single overloaded `withMongoLogging()`that silently picks between a collection, a URI, and a config. `MongoDbLogger` itself has no implicit default destination: constructing it directly without a `writer` throws, so a misconfigured logger fails fast at startup instead of silently writing logs nowhere.

### 2.0 migration notes

[](#20-migration-notes)

- Guzzle 7.10+ or 8 and `mongodb/mongodb:^2.0` are required. Laravel MongoDB and `ClientRequestLog` are removed.
- `ext-mongodb` is an install-time requirement. Review [UPGRADE-2.0.md](UPGRADE-2.0.md) before upgrading.
- Mongo logging uses native-driver PSR-3 event documents with bounded driver timeouts.
- WAN IP and body logging remain opt-in.

Quality Assurance
-----------------

[](#quality-assurance)

The repository uses the DTO-style quality contract with a few client-specific additions.

```
composer check
```

Run `composer lint:all` and `composer test` directly when you want the underlying steps separately; use `composer check` for the standard combined gate.

Additional validation commands:

- `composer lint:fix`
- `composer test:coverage`
- `composer bench`
- `composer ci`

Intentional client-specific differences from the DTO baseline:

- 98% coverage gate on `composer test:coverage`
- dedicated benchmark workflow with PHPBench
- optional live-network workflow for real external IP logging checks
- active CI secret scanning via `secret-scanning.yml`

Repository-standard auxiliary automation now also matches DTO more closely:

- semantic PR titles require an uppercase subject
- pull requests are auto-labeled with DTO-style label categories
- releases validate tags before publishing GitHub releases and can notify Packagist when credentials are configured

Coverage remains an intentional client-specific divergence: this repo keeps a 98% gate and a narrower excluded-source set so the enforced threshold stays meaningful for the exercised client runtime surface.

AI Development Workflow
-----------------------

[](#ai-development-workflow)

This package includes AI-oriented scaffolding to keep delivery consistent with quality gates.

- Agent guidance: [AGENTS.md](AGENTS.md), [CLAUDE.md](CLAUDE.md)
- Tooling folders: `.claude/commands`, `.cursor/rules`, `ai/skills`, `antigravity/prompts`, `jetbrains/prompts`
- Development process references: [docs/04-development](docs/04-development), [docs/05-maintenance](docs/05-maintenance)

When AI changes code, run:

```
composer check
```

Docker Development
------------------

[](#docker-development)

If PHP is not installed locally, run everything in Docker.

```
docker compose up -d --build mongodb
docker compose run --rm php composer install
docker compose run --rm php composer test
```

For live network integration tests (real sites), run:

```
docker compose run --rm -e JOOCLIENT_RUN_LIVE_NETWORK_TESTS=1 php \
    vendor/bin/phpunit tests/Feature/Logging/RealSiteIpLoggingTest.php
```

This test hits:

- `https://httpbin.org/get`
- `https://example.com`
- `https://google.com`

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md) for details.

Normal feature and fix work branches from `develop` and PRs back into `develop`. Release preparation uses `release/` branches from `develop` into `master`.

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance97

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 85.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~19 days

Total

10

Last Release

14d ago

Major Versions

0.5.0 → 1.0.02026-03-08

v1.5.0 → v2.0.02026-07-21

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/142772948?v=4)[JOOservices Ltd](/maintainers/jooservices)[@jooservices](https://github.com/jooservices)

---

Top Contributors

[![soulevilx](https://avatars.githubusercontent.com/u/2688707?v=4)](https://github.com/soulevilx "soulevilx (88 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (15 commits)")

---

Tags

asynccachecircuit-breakerconcurrencydtoguzzlehttp-clientjooservicesloggingphpphp85psrpsr-16psr-3retryasynchttp clientGuzzleMetricstracingcacheretryrate limitdtocircuit breakerJOOservices

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jooservices-client/health.svg)

```
[![Health](https://phpackages.com/badges/jooservices-client/health.svg)](https://phpackages.com/packages/jooservices-client)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k19](/packages/tempest-framework)[laravel/framework

The Laravel Framework.

34.9k556.2M21.1k](/packages/laravel-framework)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M651](/packages/shopware-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

762297.9k49](/packages/civicrm-civicrm-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
