tuki / http-client
Framework-agnostic HTTP client core package.
Requires
- php: 7.4.* || 8.*
- ext-curl: *
- ext-json: *
- guzzlehttp/guzzle: ^7.7
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
README
This package wraps Guzzle with a fluent API that adds lifecycle hooks, named configuration profiles, extended JSON support, and a built-in fake for testing — all without any framework dependency.
Reach for it when you need consistent behavior across every outbound request (logging, metrics, etc.), want to manage multiple API targets under a single factory, or just want better tooling for testing HTTP interactions without real network calls.
The package only requires Guzzle, ext-curl, and ext-json. There is a companion package tuki/http-client-for-laravel that wires everything into Laravel — facade, container binding, dedicated config file, queue-based async dispatch, database persistence via migrations, and framework events. If you are on Laravel, start there instead.
Requirements
- PHP:
7.4.* || 8.* - PHP extensions:
ext-curl,ext-json - Runtime dependency:
guzzlehttp/guzzle:^7.7
The package uses a CI pipeline that executes tests in PHP >= 7.4 (PHP 8.5 and above still missing but should be supported soon) and runs phpstan.
Installation
This is a standard packagist package. You can install it in your project with:
composer require tuki/http-client
Start using it by importing the factory and defining a default profile and options. Then do requests directly (by using the default profile) or in a specific profile.
use Tuki\HttpClient\Factories\ClientFactory;
use Tuki\HttpClient\Factories\SinkFactory;
// Build a factory somewhere in your start-up phase with your desired configuration.
$factory = new ClientFactory([
// What profile use by default, if no profile is specified
'default_profile' => 'default',
// Default profile options; if the profile doesn't define an option, the default from this will be used
'profile_defaults' => ['connect_timeout' => 10, 'timeout' => 20, 'sinks' => []],
'profiles' => [
// Definition of "default" profile. Will inherit all "profile_defaults" options.
'default' => [],
// Definition of another profile with specific options.
'example-profile' => ['base_uri' => 'https://api.example.test', 'connect_timeout' => 15, 'timeout' => 30],
],
], new SinkFactory([]));
// Use the factory to make a request through the "default" profile.
// The string in `newRequest()` acts as a label or description of the request.
$responseFromDefaultProfile = $factory->newRequest('health-check')
->to('GET', 'https://api.example.test/health')
->execute();
// Use the factory with a specific profile to use its options by default
$responseFromExampleProfile = $factory->profile('example-profile')->newRequest('health-check')
// Full url is not needed as it is defined in the profile. Timeout will be overridden too.
->to('GET', '/health')
->execute();
// You can get the Guzzle client from a profile and make requests directly.
// Useful when you have code that already uses Guzzle directly.
// The trade-off: per-request labels are not available this way.
$guzzleClient = $factory->profile('example-profile')->getInternalClient();
Worth knowing: the SinkFactory passed to the factory is where sinks come from. Each attached sink automatically receives the result of every request made through its profile — a handy hook for logging, metrics, or persisting requests to a database. Profiles can also attach a SealProvider, which is the reusable authentication hook that seals every outgoing request for that profile. See Sinks and extractors and Authentication and seal providers for the full picture.
For a complete setup walkthrough, see Installation and first success.
How it works
The clients built by the factory carry a custom Guzzle middleware that intercepts each outgoing request. This is how profile-specific options (base URI, timeouts, headers) are applied, and how sinks receive the request result after execution.
The Sink contract is built on top of that mechanism. A profile can attach one or more sinks, and after each request completes, every attached sink gets the full request/response payload. Common uses: writing structured logs, persisting request records to a database, feeding metrics to a Prometheus exporter.
Documentation
- Documentation index
- Installation and first success
- Making requests
- Configuration and profiles
- Authentication and seal providers
- Sinks and extractors
- Hooks and failure policy
- Testing
- Migration and adoption
- Troubleshooting
- Contributing
License
MIT. See LICENSE.
Validation
composer install
composer test
composer stan
composer coverage
You can run this commands in any supported PHP version with ext-curl and ext-json.
The pipeline and the recommended development environment is provided as a docker set-up in .docker/. The recommended way to execute tests and other commands is the following:
docker compose -f .docker/docker-compose.yml run -it --rm app vendor/bin/phpunit # or any other php command
In the docker-compose.yml file are services defined for each supported PHP version.
From the workspace root, the recommended execution path is the root Makefile:
make core-qa
make core-coverage
Inside this package, use Makefile targets directly. They support both local Docker and CI/direct execution:
make qa # local default: Docker mode
make qa USE_DOCKER=0 # CI/direct mode (no docker compose wrapper)