ezimuel/ringphp
RingPHP provides a lightweight, PSR-7–focused abstraction for HTTP clients and servers, inspired by the Ring specification. Compose middleware-style handlers, adapt to popular transports, and build reusable, testable HTTP stacks without locking into a single client.
Installation
composer require ezimuel/ringphp
First Use Case: Wrapping a Request RingPHP is primarily used to wrap HTTP requests (e.g., for testing or middleware injection). Start with a simple example:
use Ezimuel\RingPHP\Ring;
use GuzzleHttp\Psr7\Request;
$request = new Request('GET', 'https://example.com/api');
$response = Ring::run($request, function ($request) {
// Modify or inspect the request here (e.g., add headers)
$request = $request->withHeader('X-Custom-Header', 'value');
return $request;
});
src/Ring.php (core logic)src/Response.php (response handling)RingPHP excels at stackable middleware—ideal for testing or modifying requests/responses in Elasticsearch clients (e.g., elasticsearch/elasticsearch).
Example: Logging Middleware
use Ezimuel\RingPHP\Ring;
use Psr\Http\Message\RequestInterface;
$middleware = function (callable $next) {
return function (RequestInterface $request) use ($next) {
logger()->info('Request:', ['method' => $request->getMethod(), 'uri' => (string)$request->getUri()]);
return $next($request);
};
};
$response = Ring::run($request, $middleware);
Replace real HTTP clients (e.g., Guzzle) with RingPHP in unit tests:
public function testSearchQuery()
{
$mockResponse = new Response(200, [], json_encode(['hits' => []]));
$request = new Request('GET', '/search');
$response = Ring::run($request, function ($req) use ($mockResponse) {
return $mockResponse; // Short-circuit to return mock
});
$this->assertEquals(200, $response->getStatusCode());
}
Use RingPHP to intercept and modify Elasticsearch requests:
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()
->setHosts(['http://localhost:9200'])
->setHandler(Ring::createHandler(function ($request) {
// Add auth token to every request
return $request->withHeader('Authorization', 'Bearer token');
}))
->build();
Modify responses before they reach the application:
$response = Ring::run($request, function ($req) {
return Ring::run($req, function ($req) {
// First middleware (e.g., auth)
return $req;
}, function ($res) {
// Second middleware: transform response
$data = json_decode($res->getBody(), true);
return new Response($res->getStatusCode(), [], json_encode(['transformed' => $data]));
});
});
No PSR-15 Middleware Support
MiddlewareInterface). Avoid mixing with frameworks like Lumen/Laravel that expect PSR-15.Response Handling Quirks
run() expects the final middleware to return a Response or StreamInterface. Forgetting this causes RuntimeExceptions.InvalidArgumentException if responses are malformed.Elasticsearch-PHP Specifics
elasticsearch/elasticsearch, ensure the handler stack is properly configured. Misconfigured handlers may silently fail.ClientBuilder::setHandlerStack() to debug:
$client->getHandlerStack()->push(Ring::createHandler(...));
Performance Overhead
PHP 8.5 Compatibility
curl_close() calls in PHP 8.5 environments, avoiding potential deprecation warnings or errors.curl_close() in custom middleware, ensure your code is compatible with PHP 8.5’s stricter handling of cURL resources.Ring::run($request, function ($req) {
logger()->debug('Request:', (string)$req);
return $req;
});
$handler = Ring::createHandler(...);
$handler->__invoke($request); // Manually trigger to debug
Custom Response Classes
Extend Ezimuel\RingPHP\Response to add domain-specific logic:
class ElasticResponse extends Response {
public function getHits() { /* ... */ }
}
Async Support (Experimental) RingPHP is synchronous, but you can wrap async logic in middleware:
$middleware = function ($next) {
return function ($req) use ($next) {
return \React\Promise\resolve($next($req));
};
};
Integration with Laravel
Use Ring::run() in service providers to wrap HTTP clients:
$this->app->singleton('elasticsearch', function () {
return ClientBuilder::create()
->setHandler(Ring::createHandler($this->app['middleware.elasticsearch']))
->build();
});
How can I help you explore Laravel packages today?