Roadrunner executes php-cli and connects it to its web server through GRPC; FrankenPHP uses an ad-hoc SAPI, it is more like Apache's mod_php, the Go code uses the PHP interpreter as a library, it's all in the same process.
RoadRunner only has a worker mode, and can only work with compatible apps; FrankenPHP has a "standard" mode compatible with all existing applications, and a worker mode that requires some code changes (like RR).
RoadRunner runner uses PSR-7 HTTP messages; FrankenPHP uses plain old superglobals and streams (both in normal and in worker modes), they are reset after each handled request.
RoadRunner is battle-tested and production ready; FrankenPHP is experimental and not yet ready for production use.
FrankenPHP can also be used as a Go library to integrate PHP into any Go program or server (theoretically, it should be possible to integrate FrankenPHP into Traefik or the local Symfony web server, which are written in Go).
Hey @kdunglas :) Nice project :) I just wanted to add a few notes:
> Roadrunner executes php-cli and connects it to its web server through GRPC;
RR communicates with the PHP process via pipes (stdin/out/err) via our protocol (similar to the IP protocol, we also have a tcp/unix-socket options and shared memory is coming soon). The RR server itself then has various options to connect to the world:
- gRPC (w/o the need to install the `gRPC-php` extension), HTTP (with a lot of different middleware), HTTPS, fCGI.
- Queues: kafka, rabbitmq, sqs, beanstalk, nats (with like priorities and so on)
- KV/noSQL: memcached, redis, boltdb, memory.
- Workflows: Temporal.
I might forget smt, but the main idea is to have a very extensible server. With very little knowledge about Go, you may extend it with your plugins and build your RR binary. Or even embed the RR server into your Go application (https://github.com/roadrunner-server/roadrunner/tree/master/...).
Just some experiences I've had putting large PHP frameworks into strange spaces:
1. Most PHP frameworks are designed to have all state destroyed at the end of a request. I was trying to integrate a commercial ecommerce framework with something like Road Runner and another one that I forget the name of. The framework had a DI system which provides each module with its own private instance of all injected instances, so having a "worker" that doesn't "boot" everything each request sounded like a good idea (boot was expensive, and a lot of logic was storing module-specific state in module-private instances). I hit a few barriers inside the framework, but actually a lot of them were due to dependencies on PHP global state following state-of-the-art conventions and best practices. It lead to spooky side-effects like cache from one page view loading into the next, and worse. Getting frameworks to run in a loop in PHP can often lead to sharing state in code that was designed in a way that state is assumed to be destroyed soon.
2. PHP depends on lots of unexpected things. If you're deep into language internals already you probably know this however. I was putting symfony2 into a PHP Unikernel a long time ago, and it drove me a bit crazy because everything in the file system, SAPI, locales, etc... it was all missing bridges to something it expected the OS to provide. I ended up making an immutable FS with Nginx and PHP all static linked to each other, but it was really just enough for a POC, a real production ready env would have been a lot more effort. The point is, PHP has a lot of unexpected "hooks" into environments it has grown up around that might be well hidden.
Anyway, really cool project and I like the concept of using a SAPI, I think it has big potential.
Re (1) how feasible would it be to basically teach the PHP VM the equivalent of fork() so you can do all the booting once but still have a fresh copy of the end result on each request?
I mean, it's not going to be as cheap as getting the thing to actually run in a loop, but it might be enough cheaper than the booting process to be worthwhile.
This is one of the things FrankenPHP is dealing with (w/out fork).
The big 'gotcha' here is that the heap is typically shared amongst threads in a process and that's where globals tend to live. However, you could make a heap per thread (which is kind of how some implementations of isolates work). You lose a bit of perf by doing this but it does deal with the global stomping problem.
We've (NanoVMs) looked at this a few times. It can be done but as bdg mentioned most frameworks expect state to be in a completely clear so the real challenge is that you have to go in and deal with each framework itself (for instance using WP as an example).
If you had a php framework that wasn't so dependent on global state you could definitely make something way more performant.
In general scripting languages and their usage of global state is a recurring concurrency issue but I'm hopeful that the isolate pattern will catch on in other languages to help alleviate it.
It's a cool idea but I haven't explored this in detail so I don't have a good answer. I haven't touched PHP in 2 years but they have a new feature which might achieve some of those design goals you mentioned:
> preload.php is an arbitrary file that will run once at server startup (PHP-FPM, mod_php, etc.) and load code into persistent memory.
It's interesting to me, because in a big ecomm framework we were getting 80ms PHP page loads, with something like preload it could probably be moved down to 25 or 30ms.
Roadrunner executes php-cli and connects it to its web server through GRPC; FrankenPHP uses an ad-hoc SAPI, it is more like Apache's mod_php, the Go code uses the PHP interpreter as a library, it's all in the same process.
RoadRunner only has a worker mode, and can only work with compatible apps; FrankenPHP has a "standard" mode compatible with all existing applications, and a worker mode that requires some code changes (like RR).
RoadRunner runner uses PSR-7 HTTP messages; FrankenPHP uses plain old superglobals and streams (both in normal and in worker modes), they are reset after each handled request.
RoadRunner is battle-tested and production ready; FrankenPHP is experimental and not yet ready for production use.
FrankenPHP can also be used as a Go library to integrate PHP into any Go program or server (theoretically, it should be possible to integrate FrankenPHP into Traefik or the local Symfony web server, which are written in Go).