Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

React Adapter Laravel Package

php-http/react-adapter

ReactPHP adapter for the PHP-HTTP httplug abstraction. It lets you send asynchronous, non-blocking HTTP requests through ReactPHP's event loop using the standard httplug and PSR-7 interfaces, so existing client code keeps working unchanged.

Deep Wiki
Context7

Getting Started

php-http/react-adapter is a thin HTTPlug bridge to ReactPHP's react/http Browser. It gives you an Http\Client\HttpAsyncClient implementation that runs on your existing event loop, so HTTP calls no longer block a long-running ReactPHP process. It is async-only — there is no synchronous HttpClient/PSR-18 implementation.

Install it with the PSR-7 implementation of your choice:

composer require php-http/react-adapter react/event-loop react/http guzzlehttp/psr7

Wire it to the shared loop. The constructor takes the LoopInterface (recent versions also accept a pre-built React\Http\Browser as a second argument so you can configure timeouts, redirects, TLS and connectors):

<?php
require 'vendor/autoload.php';

use Http\Adapter\React\ReactAdapter;
use GuzzleHttp\Psr7\Request;
use React\EventLoop\Loop;

$adapter = new ReactAdapter(Loop::get());

$request  = new Request('GET', 'https://httpbin.org/get');
$response = $adapter->sendAsyncRequest($request)->wait(); // runs the loop until settled

echo $response->getStatusCode(), "\n", (string) $response->getBody(), "\n";

That wait() is your first use case: in a plain CLI script it behaves like a normal blocking client. The moment you are inside a running loop, you switch to then() callbacks (see below).

Where to look first:

  1. The README — it is short and is the authoritative source for the constructor signature on your tag.
  2. src/ReactAdapter.php — it is small; confirm how it builds or accepts the Browser and how it maps exceptions.
  3. The promise wrapper (e.g. ReactPromise) — this is where wait() and state mapping live.
  4. tests/ for the exact error/exception behaviour, then ReactPHP's own Browser docs for anything HTTP-level (timeouts, redirects, connectors).

Implementation Patterns

Non-blocking calls inside a running loop. Never call wait() from inside a loop callback. Attach handlers and let the loop drive:

$adapter->sendAsyncRequest($request)->then(
    function (Psr\Http\Message\ResponseInterface $response) {
        echo (string) $response->getBody();
    },
    function (\Throwable $e) {
        error_log(get_class($e) . ': ' . $e->getMessage());
    }
);

Loop::get()->run(); // or just return if a server/timer already runs the loop

Fan-out / concurrency. Fire all requests first, then let the loop drain them. Two common shapes:

$pending = count($urls);
$results = [];

foreach ($urls as $i => $url) {
    $adapter->sendAsyncRequest(new Request('GET', $url))->then(
        function ($res) use ($i, &$results, &$pending) { $results[$i] = $res; $pending--; },
        function ($e)  use ($i, &$results, &$pending) { $results[$i] = $e;  $pending--; }
    );
}

Loop::get()->run();
// $pending === 0 here, $results holds responses and exceptions in input order

If you prefer React\Promise\all(), convert the HTTPlug promise to a React one:

use Http\Promise\Promise as HttpPromise;
use React\Promise\Promise as ReactPromise;
use function React\Promise\all;

$toReact = fn (HttpPromise $p) => new ReactPromise(
    fn (callable $resolve, callable $reject) => $p->then($resolve, $reject)
);

all(array_map($toReact, $promises))->then(function (array $responses) { /* ... */ });

Use PluginClient for cross-cutting concerns. Because the inner client is async, the plugin client stays async:

use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\HeaderSetPlugin;
use Http\Client\Common\Plugin\RetryPlugin;
use Http\Client\Common\Plugin\ErrorPlugin;

$client = new PluginClient($adapter, [
    new HeaderSetPlugin(['User-Agent' => 'billing-worker/1.0']),
    new RetryPlugin(['retries' => 2]),
    new ErrorPlugin(), // turn 4xx/5xx into HttpException
]);

$client->sendAsyncRequest($request)->then(/* ... */);

Only call sendAsyncRequest() on the PluginClient; calling sendRequest() will fail because the wrapped client is not synchronous. Check that any third-party plugin implements the async side (AsyncPlugin) before assuming it applies.

Share the loop with the rest of the app. The whole point of the package is that HTTP I/O cooperates with your react/http server, WebSocket handler, timers and DB driver. Inject one ReactAdapter (or one PluginClient around it) as a singleton in your container and always construct it with Loop::get() / the same loop instance used everywhere else.

Sync facade for legacy code. If a small part of the codebase still expects Http\Client\HttpClient, write a narrow adapter that calls sendAsyncRequest(...)->wait(), and only use it from entry points that are not already inside the loop.

Testing. php-http/mock-client implements HttpAsyncClient and returns already-fulfilled promises; use it for unit tests of your callers. For real integration tests, spin up a local React\Socket\SocketServer and reset the global loop between tests (Loop::set(new React\EventLoop\StreamSelectLoop())) so state does not leak.

Gotchas and Tips

  • wait() inside a running loop deadlocks or throws. React's loop cannot be run re-entrantly. Inside any then(), timer or server callback, use then() or convert to a React promise and React\Async\await().
  • Async-only. There is no PSR-18 client here. Don't expect Http\Discovery\Psr18ClientDiscovery or libraries type-hinting Http\Client\HttpClient to accept it. Wrap it explicitly if you need a sync surface.
  • Don't rely on auto-discovery. The adapter needs a loop (and possibly a Browser), so letting HttpAsyncClientDiscovery instantiate it is fragile. Register it manually in your container.
  • HTTP-level behaviour lives in React\Http\Browser, not in HTTPlug options. Timeouts, redirect following, TLS/CA, proxies and connectors are Browser concerns. The adapter does not interpret Guzzle-style RequestOptions. If your version's constructor only takes the loop, that is the first limitation to work around — pass or wrap a configured Browser.
  • Redirects can be applied twice. React's Browser follows redirects by default; adding RedirectPlugin on top can change method/body semantics or hide the 3xx. Pick one layer and disable the other.
  • No default timeout. A hung peer will keep the promise pending forever. Set ->withTimeout(10.0) on the Browser (or pass a configured Browser) and let the resulting rejection surface as a network exception.
  • Responses are buffered. Browser resolves with a fully buffered PSR-7 response, and request bodies are sent as strings. Large downloads/uploads cost memory and are not streamed through this adapter. For streaming use react/http directly.
  • Exception mapping. Connection refused, DNS failure, TLS errors and timeouts arrive as Http\Client\Exception\NetworkException (or a sibling). 4xx/5xx are not exceptions unless you add ErrorPlugin — without it they are fulfilled responses. Catch Http\Client\Exception broadly plus \Throwable.
  • One loop, one adapter. If you pass a Browser bound to a different loop than the adapter runs, wait() will run the wrong loop and hang. Always use Loop::get() consistently.
  • HTTP/1.1 only. No HTTP/2, no automatic proxy env-var handling, and DNS may fall back to blocking resolution on some platforms (notably Windows). Plan accordingly for high-fan-out crawlers.
  • Lifecycle. In a daemon, never call Loop::run() yourself after the server is started; just attach then() handlers. In a script, Loop::run() (or wait()) is fine, but call it once at the top level.
  • Version drift. Last release was November 2022, against react/http 1.x and react/promise 2.x/3.x. Pin versions and smoke-test on PHP 8.2+; react/promise v3 changed several type signatures.
  • Extension point. The cleanest way to add logging, metrics or tracing is a decorator around the adapter (HttpAsyncClient) or an async plugin in PluginClient, not a subclass — the class is small and may be final.
  • Fallback plan. The wrapper is thin enough that dropping it and calling React\Http\Browser directly is a realistic migration if the package stops receiving updates; keep your HTTP calls behind one small interface so that swap is cheap.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core