Usage Example

This page contains a full example about using TwirPHP on both server and client side. Much of it is based on the original usage example, so you might want to check that out as well. You can find the code for this example in the demo-project repository.

Before moving forward, make sure to check the Installation guide as well.

Write a Protobuf service definition

Write a protobuf definition for your messages and your service and put it in proto/service.proto. The following example is taken from the original Twirp documentation.

syntax = "proto3";

package twirp.example.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.e. "bowler"
}

Generate code

To generate code run the protoc compiler pointed at your service definition file:

$ mkdir -p generated
$ protoc -I . --twirp_php_out=generated --php_out=generated ./proto/service.proto

This will generate the standard PHP messages along with the Twirp specific files.

Implement the server

Now that everything is in place, it’s time to implement the server implementing the service interface (Twirp\Example\Haberdasher\Haberdasher in this case).

<?php

namespace Twirp\Demo;

use Twirp\Example\Haberdasher\Hat;
use Twirp\Example\Haberdasher\Size;

final class Haberdasher implements \Twirp\Example\Haberdasher\Haberdasher
{
    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;
    }
}

Run the server

To run the server you need to setup some sort of application entrypoint that processes incoming requests as PSR-7 messages. It is recommended that you use some sort of dispatcher/emitter, like the SapiEmitter bundled with Zend Diactoros, but the following example works perfectly as well:

<?php

require __DIR__.'/vendor/autoload.php';

$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();

$server = new \Twirp\Server();
$handler = new \Twirp\Example\Haberdasher\HaberdasherServer(new \Twirp\Demo\Haberdasher());
$server->registerServer(\Twirp\Example\Haberdasher\HaberdasherServer::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();

Use the client

Client stubs are automatically generated, hooray!

There are two client stubs generated for each proto service: the default one which uses protobuf serialization, and a JSON client stub. Twirp itself supports both protobuf and json, but it recommends using only protobuf in production.

Using the client is quite trivial, you only need to pass an endpoint to the generated client:

<?php

require __DIR__.'/vendor/autoload.php';

$client = new \Twirp\Example\Haberdasher\HaberdasherClient($argv[1]);

while (true) {
    $size = new \Twirp\Example\Haberdasher\Size();
    $size->setInches(10);

    try {
        $hat = $client->MakeHat([], $size);

        printf("I received a %s %s\n", $hat->getColor(), $hat->getName());
    } catch (\Twirp\Error $e) {
        if ($cause = $e->getMeta('cause') !== null) {
            printf("%s: %s (%s)\n", strtoupper($e->getErrorCode()), $e->getMessage(), $cause);
        } else {
            printf("%s: %s\n", strtoupper($e->getErrorCode()), $e->getMessage());
        }
    }

    sleep(1);
}

Warning

When no scheme is present in the endpoint, the client falls back to HTTP.