prolix/restclientbundle
Prolix RestClientBundle provides a simple REST client integration for PHP, making it easy to configure HTTP clients, send requests, and handle responses in your application. Ideal for projects that need a reusable, bundle-style API client layer.
This is a Symfony bundle wrapping PHP's native curl_* functions. There is no README-level documentation beyond the description, so treat the source as the spec.
Minimal setup:
composer require prolix/restclientbundleconfig/bundles.php:
return [
// ...
Prolix\RestClientBundle\RestClientBundle::class => ['all' => true],
];
vendor/prolixtechnikos/ci-rest-client-bundle/DependencyInjection/ (or Resources/config/services.*) to discover the actual service ID, the config root alias, and the client class name. Do this before writing config YAML — the alias rarely matches the composer name.config/packages/ using the alias you found (expect something like rest_client: with base_url, timeout, headers keys).public function __construct(private \Prolix\RestClientBundle\RestClient $client) {}
public function ping(): array
{
$response = $this->client->get('/health');
return json_decode($response->getBody(), true);
}
First real use case: a single internal, JSON-over-HTTPS endpoint where you control both sides. Do not start with a third-party API — you want to isolate "is my bundle wiring correct" from "is their API behaving."
Wrap it, don't inject it everywhere. The client is a thin transport. Create one small service per external system:
final class BillingApi
{
public function __construct(private RestClient $client) {}
public function charge(string $customerId, int $cents): array
{
$res = $this->client->post('/charges', [
'customer' => $customerId,
'amount' => $cents,
]);
if ($res->getStatusCode() >= 400) {
throw new ApiException($res->getBody(), $res->getStatusCode());
}
return json_decode($res->getBody(), true, flags: JSON_THROW_ON_ERROR);
}
}
This is the pattern that pays off day to day: verb methods stay generic, domain methods become typed and testable.
Configure base URL, auth, and timeout globally; override per call. Set base_url and default headers in the bundle config, then pass per-request headers for things like Idempotency-Key. Check the source for the merge order — most thin wrappers append per-request headers and shadow duplicates, but verify.
Handle bodies as strings, decode at the boundary. The wrapper almost certainly returns the raw cURL body. Centralize json_decode(..., JSON_THROW_ON_ERROR) in your wrapper so malformed responses surface as a clear exception rather than a null that propagates.
Test by stubbing at your wrapper boundary, not the HTTP layer. With zero tests and no PSR-18/PSR-7 interface, mocking the bundle's client is brittle. Type-hint your own BillingApi against an interface and fake that; run the real bundle client only in integration tests.
Don't mix it with Symfony HttpClient in the same service. Pick one per integration. Two different exception models and two different timeout semantics in one class is a debugging trap.
Use the compiler pass / service override for cross-cutting concerns. If you need correlation-ID headers or request logging, decorate the bundle's service definition in a Kernel::build() compiler pass rather than threading it through every call site.
CURLOPT_* behavior to surface in odd ways: timeout vs connect_timeout may not both be configurable, SSL verification may default off (grep for CURLOPT_SSL_VERIFYPEER), and redirects may be disabled by default. Audit these three before production.curl_exec per call means serial request latency. If you need concurrency, you are writing curl_multi yourself — at which point you should switch to Symfony HttpClient, which gives you HttpClientInterface plus a mockable MockHttpClient.curl_exec returns false on transport failure. Determine whether the bundle throws, returns a response with status 0, or returns false, and normalize it in your wrapper. Silent false is the most common bug here.prolix/restclientbundle maps to RestClientBundle but the DI alias could be prolix_rest_client, rest_client, or ci_rest_client. bin/console debug:container | grep -i rest finds it in seconds.services.xml uses old argument syntax or the extension class lacks Configuration return types, you may get deprecation noise or hard failures on Symfony 6/7. Test bundle boot on your exact Symfony version as step one.RequestInterface. Plan your design so all of that lives in your wrapper.CURLOPT_VERBOSE to a stream via a config option if exposed; otherwise temporarily patch your fork. Capture curl_error($ch) and curl_errno($ch) — the bundle may swallow them, and "empty 500 response" with no diagnostic is the worst failure mode this class of package produces.How can I help you explore Laravel packages today?