nesk/rialto
Rialto is a PHP package that builds a bidirectional bridge between PHP and Node.js, letting PHP call Node.js functions and exchange data through a simple synchronous API. It powers PuPHPeteer and works with custom Node.js processes.
Three steps to a working bridge:
composer require nesk/rialto (and make sure a Node.js runtime is on the machine/PATH; Rialto shells out to node).nesk/php-chrome-remote-interface and nesk/php-webdriver by the same author. Read one of those before writing your own process class; the abstraction makes far more sense when you see a full example.Nesk\Rialto\AbstractProcess, point it at a JS entry script, and declare an Instructions class that maps each PHP method to a snippet of JavaScript.A minimal process class looks roughly like:
class MyProcess extends AbstractProcess
{
protected function getDefaultOptions(): array
{
return ['nodeExecutable' => 'node'];
}
protected function getInstructionsClass(): string
{
return MyInstructions::class;
}
protected function getScriptPath(): string
{
return __DIR__ . '/../resources/bridge.js';
}
}
Then, from your application code, $process->createResource() opens a handle to a Node-side object, and calling methods on that resource invokes the matching instruction. The very first use case to validate is a trivial one: run a Node script that returns a string, call it from PHP, and echo the result. If that round-trip works, the rest of the abstraction is just ergonomics.
Instruction-driven API surface. Every method you want to call from PHP needs a corresponding instruction. Instructions are declared with a name, an argument list, and a JavaScript handler. The handler receives the resource's underlying Node object plus the serialized arguments. This means your PHP-facing API is fully described up front rather than reflected dynamically — good for reliability, slightly verbose.
Resource handles, not values. Resources returned from Node are wrappers (AbstractResource subclasses), not plain data. That lets you chain calls: $page->navigate($url)->waitForSelector('h1')->textContent(). Model your domain objects as subclasses of AbstractResource and each chained call becomes one instruction. Return scalars and arrays directly where possible to avoid leaking handles into business logic.
Async via promises. Node calls that are asynchronous can return a promise, which PHP resolves through the process loop. This is what makes the package usable for headless-browser automation (page loads, network requests). Use the promise resolution in a loop and be aware that blocking waits block the PHP process — design your call graph so long asynchronous work is started eagerly and awaited once.
Instructions as the seam. Put every Node interaction behind an instruction; never file_get_contents a Node script ad-hoc. This keeps the JS surface versionable and testable independently. It also means you can stub instruction results in PHP tests without spawning Node.
Lifecycle management. Processes have explicit start/stop. Wrap them in a try/finally or a service container binding with a shutdown hook so a crashed PHP request doesn't leave orphaned node processes. For long-lived workers, reuse one process across many resources rather than spawning per request.
Integration with framework code. In Laravel, register the process as a singleton and inject it. Because the process owns the node child, you want it created once per request/job and closed when done. For queue workers, keep it alive across jobs if the Node side is stateless; recreate if the JS module accumulates state.
worker_threads semantics), expect friction. Pin Node to an older LTS (14/16 era) or be prepared to patch the bridge script yourself. Weigh this heavily before adopting for new work.DateTime, closures, resources, and cyclic structures will not survive. Convert to scalars before calling into the bridge, and convert back on the PHP side.pcntl_alarm (CLI only, not under FPM) or a supervising loop that kills the process.setPathToNode matters on CI. The default node lookup fails in environments where Node is installed at a nonstandard path or under nvm. Set the executable path explicitly via process options; same for the bridge script path, which is resolved relative to the package otherwise.AbstractProcess options for the debug flag in your version) prints each message sent and received. This is the single most useful troubleshooting tool when an instruction silently returns null.Instructions class and add methods. Composition of instruction sets lets you layer domain-specific helpers on top of a generic bridge without forking.How can I help you explore Laravel packages today?