TwirPHP: A modern RPC framework for PHP

How to track the progress?Alternatively, we could do something like this:POST /books/1/formats/epub HTTP/1.

1Host: api.


comContent-Type: application/json{ "fromFormat": "mobi"}This is somewhat better, because we can treat the new format as a separate resource, track it’s progress without leaving the book resource itself in an inconsistent state.

Now let’s see an RPC solution:POST /books/convert HTTP/1.

1Host: rpc.


comContent-Type: application/json{ "id": 1, "from": "mobi", "to": "epub"}Most of the time we can find a solution that somewhat fits into REST, but it’s not always possible and not always perfect.

In any case, using RPC for actions expresses the intent better and makes the API easier to understand.

Furthermore, the above action can directly be mapped to a function call:<?phpfunction convert(int $id, string $from, string $to): void { // real implementation or an HTTP call}This example not only shows the difference between REST and RPC style APIs, but also shows that the two are not mutually exclusive.

Modern RPC frameworksRPC frameworks came a long way and adapted to technology needs over time.

First and foremost: new IDLs (Interface Definition Language) were created with binary format for serialization (like Protocol Buffers or Apache Thrift).

This makes them not only faster, but thanks to their language independent protocol specifications they are also language agnostic which is a real advantage in polyglot systems.

In addition, it’s also common these days that RPC frameworks have builtin support for secure communication, authentication, load balancing and a bunch of other things, often based on the underlying protocols (eg.


One of the most popular RPC frameworks today is gRPC.

It’s an open source RPC framework, initially developed by Google.

It uses HTTP/2 for transport, Protocol Buffers as IDL and provides features such as authentication, bidirectional streaming, cancellation, timeouts, etc.

Well-known projects, like Kubernetes, etcd and many more use it for their APIs.

Another popular framework is Apache Thrift which is actually both an IDL and an RPC framework.

It was developped by Facebook, but it’s now an open source project in the Apache Software Foundation.

Introducing TwirpTwitch released it’s own RPC framework Twirp a little over a year ago.

It was designed to be an alternative to gRPC, primarily for internal usage, but seeing how quickly their teams adopted it, they decided to open source it.

In their introductory post (which I really recommend you to read) they explain in detail why they decided to implement their own RPC framework instead of just using gRPC.

The most important differences between Twirp and gRPC are HTTP 1.

1 support and JSON serialization (in addition to protobuf) which really proves to be useful during development and makes the framework easier to understand.

Twirp also comes with a thinner runtime library and relies on the generated stubs instead.

This was a design decision based on past bad experience with incompatibilities and breaking changes between different gRPC versions.

Twirp’s simplicity and lightweight nature comes at a price though: it lacks features, like client side load balancing and bidirectional streaming (at the time of writing, but it’s work in progress) so for those who rely on these advanced features Twirp might not be the right tool, but it should be enough for most of the use cases of web-based APIs.

TwirPHP: Twirp ported to PHPGiven gRPC’s popularity, Twirp might not be the first candidate for porting.

Unfortunately, gRPC’s HTTP/2 requirement makes implementing the server side non-trivial.

However, the more time I spent porting Twirp the more I became confident that it’s probably a better choice after all.

I already glorified Twirp for its JSON and HTTP 1.

1 support.

Given the primary use cases of PHP and that those use cases usually involved RESTish APIs so far, it’s much more easier to transition to a new API with similar characteristics.

Also, PHP might not be the first choice for applications using bidirectional streaming, so the lack of it is probably not a problem.

Ultimately, I believe that porting Twirp to PHP was much easier than porting gRPC would have been, thanks to its simplicity.

The latest version of TwirPHP is 0.


1 at the time of this writing.

It received a major update recently, switching to PSR-15, PSR-17 and PSR-18 from HTTPlug and dropped support for PHP 5.


ExampleTalk is cheap, let’s see some code.

( TL;DR: You can find the end result here.

)As a first step, please make sure the following components are installed in your environment:PHP 7.

1 or laterComposerProtobuf compilerTwirPHP protoc pluginNext you will have to decide how you want to install the Protobuf library:By installing the C extensionVia Composer, installing the PHP implementationThe extension is obviously faster, but at the time of writing it hasn’t been tested on PHP 7 and Windows/Mac, so for better compatibility, I will use the PHP implementation in the example.

Note: The PHP protobuf implementation needs the bcmath extension to be installed to work with JSON.

Let’s create the project and install the necessary dependencies:mkdir examplecd examplecomposer init –name twirphp/demo –type project –description "TwirPHP demo project" –no-interactioncomposer require twirp/twirp google/protobuf php-http/guzzle6-adapter http-interop/http-factory-guzzleThe next step is creating the protobuf definition for the API.

For now I will just use the one from the original Twirp documentation:syntax = "proto3";package twirp.


haberdasher;option go_package = "haberdasher";// Haberdasher service makes hats for clients.

service Haberdasher { // MakeHat produces a hat of mysterious, randomly-selected color!.rpc MakeHat(Size) returns (Hat);}// Size of a Hat, in inches.

message Size { int32 inches = 1; // must be > 0}// A Hat is a piece of headwear made by a Haberdasher.

message Hat { int32 inches = 1; string color = 2; // anything but "invisible" string name = 3; // i.


"bowler"}Let’s create it as proto/haberdasher.


Use the Protobuf compiler to generate the necessary code from the definition:mkdir -p generatedprotoc -I .

–twirp_php_out=generated –php_out=generated .


protoThe generated code includes a service interface (generated/Twirp/Example/Haberdasher/Haberdasher.

php) which should look something like this:<?php# Generated by the protocol buffer compiler (protoc-gen-twirp_php 0.



DO NOT EDIT!# source: proto/haberdasher.

protodeclare(strict_types=1);namespace TwirpExampleHaberdasher;/** * Haberdasher service makes hats for clients.

* * Generated from protobuf service <code>twirp.



Haberdasher</code> */interface Haberdasher{ /** * MakeHat produces a hat of mysterious, randomly-selected color!.* * Generated from protobuf method <code>twirp.



Haberdasher/MakeHat</code> * * @throws TwirpError */ public function MakeHat(array $ctx, TwirpExampleHaberdasherSize $req): TwirpExampleHaberdasherHat;}This is the interface which we will have to implement (src/Haberdasher.

php) and pass to the server stub later:<?phpnamespace TwirpDemo;use TwirpExampleHaberdasherHat;use TwirpExampleHaberdasherSize;final class Haberdasher implements TwirpExampleHaberdasherHaberdasher{ private $colors = ['golden', 'black', 'brown', 'blue', 'white', 'red']; private $hats = ['crown', 'baseball cap', 'fedora', 'flat cap', 'panama', 'helmet']; public function MakeHat(array $ctx, Size $size): Hat { $hat = new Hat(); $hat->setInches($size->getInches()); $hat->setColor($this->colors[array_rand($this->colors, 1)]); $hat->setName($this->hats[array_rand($this->hats, 1)]); return $hat; }}In order for the generated code and our implementation to work, we have to add them to the Composer autoloader:{ "autoload": { "psr-4": { "TwirpDemo": "src/", "": ["generated/"] } }}Make sure to dump the autoloader:composer dump-autoloadThe last step is wiring everything together.

Since this is highly application dependent, I will just use the simplest possible example here (server.

php), but you could use whatever router, message implementation, application environment you want:<?phprequire __DIR__.


php';$request = GuzzleHttpPsr7ServerRequest::fromGlobals();$server = new TwirpServer();$handler = new TwirpExampleHaberdasherHaberdasherServer(new TwirpDemoHaberdasher());$server->registerServer(TwirpExampleHaberdasherHaberdasherServer::PATH_PREFIX, $handler);$response = $server->handle($request);if (!headers_sent()) { // status header(sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase()), true, $response->getStatusCode()); // headers foreach ($response->getHeaders() as $header => $values) { foreach ($values as $value) { header($header.

': '.

$value, false, $response->getStatusCode()); } }}echo $response->getBody();The generated client will already implement the service interface, we just have too use it (client.

php):<?phprequire __DIR__.


php';$client = new TwirpExampleHaberdasherHaberdasherClient($argv[1]);while (true) { $size = new TwirpExampleHaberdasherSize(); $size->setInches(10); try { $hat = $client->MakeHat([], $size); printf("I received a %s %s.", $hat->getColor(), $hat->getName()); } catch (TwirpError $e) { if ($cause = $e->getMeta('cause') !== null) { printf("%s: %s (%s).", strtoupper($e->getErrorCode()), $e->getMessage(), $cause); } else { printf("%s: %s.", strtoupper($e->getErrorCode()), $e->getMessage()); } } sleep(1);}All we have to do now is start the server and run our client:php -S 0.



0:8080 server.

php# in a different shellphp client.

php http://localhost:8080You should see a client output like this:I received a black flat capI received a brown helmetI received a golden fedoraI received a brown fedoraI received a black flat capI received a brown fedoraI received a white baseball capTo sum up:We defined our API using Protocol BuffersGenerated most of the codeImplemented a single interfaceWired the implementation into a simple application using standard interfacesIt’s that simple.

You can find the code for this demo here and more information about getting started in the documentation.

ConclusionThanks to it’s simplicity Twirp is a perfect fit for most web-based use cases.

It’s easy to transition to a Twirp-based API from any previous RESTish solution.

By using open HTTP standards, TwirPHP is easy to integrate and use in any kind of projects.

Stability noteAs of 0.


0 TwirPHP is considered to be stable.

The API will not change, unless serious flaws are found.

The structure of the generated code might change but it shouldn’t affect existing applications.

After a few months of beta period the first stable version (1.


0) will be tagged.

Further readingTwirPHP documentationTo get more insights about RPC, REST and their differences I strongly suggest reading the following articles:Twirp: a sweet new RPC framework for GoUnderstanding RPC Vs REST For HTTP APIsRPC is Not Dead: Rise, Fall and the Rise of Remote Procedure CallsOriginally published at https://sagikazarmark.



. More details

Leave a Reply