Skip to main content

Quickstart

This guide walks you through the process of quickly setting up a new project with TwirPHP.

The end result and the code samples are available in this repository.

caution

This guide is merely to demonstrate the very basic features of TwirPHP.

Please read the rest of the documentation for details about running TwirPHP in production.

Prerequisites

Please install the following components using your preferred method.

tip

Give Nix a try and install dependencies in seconds.

Set up a new project

Create a new project directory:

mkdir twirphp-quickstart
cd twirphp-quickstart

Initialize Composer:

composer init --name twirp/example --stability dev --autoload src/ --no-interaction

Install the protoc plugin (code generator)

The easiest way to install the plugin is with the following script:

curl -Ls https://git.io/twirphp | bash

It will install the plugin in a directory called bin.

Alternatively, you can download a prebuilt binary from the releases page.

tip

If you use Nix, just install the protoc-gen-twirp_php from nixpkgs.

Install the runtime library

Install the runtime library (and the quickstart package) with Composer:

composer require twirp/twirp twirp/quickstart

The quickstart package installs some additional dependencies required to run TwirPHP, but for the purposes of this guide, they are not important. They are explained in more detail in the rest of the documentation.

Create a new service definition

service.proto
syntax = "proto3";

package twitch.twirp.example;

// A Hat is a piece of headwear made by a Haberdasher.
message Hat {
// The size of a hat should always be in inches.
int32 size = 1;

// The color of a hat will never be 'invisible', but other than
// that, anything is fair game.
string color = 2;

// The name of a hat is it's type. Like, 'bowler', or something.
string name = 3;
}

// Size is passed when requesting a new hat to be made. It's always
// measured in inches.
message Size {
int32 inches = 1;
}

// A Haberdasher makes hats for clients.
service Haberdasher {
// MakeHat produces a hat of mysterious, randomly-selected color!
rpc MakeHat(Size) returns (Hat);
}

Generate code

Create a new directory for the generated code:

mkdir generated/

Run the protobuf compiler. Based on where you installed the plugin, you'll have to run one of the commands below:

protoc --plugin=protoc-gen-twirp_php=bin/protoc-gen-twirp_php --twirp_php_out=generated/ --php_out=generated/ service.proto

Add autoloading configuration for the generated code to composer.json:

composer.json
{
"name": "twirp/example",
"autoload": {
"psr-4": {
"Twirp\\Example\\": "src/",
"": "generated/"
}
},
"minimum-stability": "dev",
"require": {
"twirp/twirp": "^0.8.0",
"twirp/quickstart": "dev-master"
}
}

Dump the Composer autoloader after making the above change:

composer dump-autoload

Implement the server

The next step is writing some code that fulfills the generated service interface. This will be the business logic answering to requests.

src/Haberdasher.php
<?php

namespace Twirp\Example;

use Twitch\Twirp\Example\Hat;
use Twitch\Twirp\Example\Size;

final class Haberdasher implements \Twitch\Twirp\Example\Haberdasher
{
public function MakeHat(array $ctx, Size $size): Hat
{
$hat = new Hat();
$hat->setSize($size->getInches());
$hat->setColor('golden');
$hat->setName('crown');

return $hat;
}
}

Run the server

To serve requests over HTTP, we need to turn the service implementation into a Psr\Http\Server\RequestHandlerInterface:

server.php
<?php

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

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

$handler = new \Twitch\Twirp\Example\HaberdasherServer(new \Twirp\Example\Haberdasher());

$response = $handler->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();
note

In this example there is only one service. If you need to mount more than one service in the same application, take a look at the Twirp\Router class. TODO: write a documentation page about it.

Test the server

At this point, you can start sending requests to the server.

First, launch a PHP server in a new shell:

php -S 127.0.0.1:8080 server.php

Then send an HTTP request to the server using cURL:

curl http://127.0.0.1:8080/twirp/twitch.twirp.example.Haberdasher/MakeHat \
-X POST \
-H "Content-Type: application/json" \
-d '{"inches": 123}'
{"size":123,"color":"golden","name":"crown"}

Use the generated client stubs

One of the biggest benefits of using a protobuf-based RPC framework is that you don't have to write any client code: the framework generates that for you.

Here is an example of using the generated client:

client.php
<?php

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

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

$size = new \Twitch\Twirp\Example\Size();
$size->setInches(1234);

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

echo $hat->serializeToJsonString();
} catch (\Twirp\Error $e) {
echo json_encode([
'code' => $e->getErrorCode(),
'msg' => $e->getMessage(),
'meta' => $e->getMetaMap(),
]);
}

Call the server started in the previous step:

php client.php http://127.0.0.1:8080

Conclusion

In this guide, you have learned how to use the code generator (protobuf plugin) to generate server code and client stubs from a service defined in protobuf and learned how to implement the service and integrate it into standard HTTP tooling.

You can read more about each concept presented in this guide in the following sections.