zolex / twirp-bundle
Twirp Bundle for Symfony
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: ^8.1
- ext-protobuf: *
- google/protobuf: ^3.21
- php-http/discovery: ^1.14
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- symfony/config: ^6.4|^7.0
- symfony/dependency-injection: ^6.4|^7.0
- symfony/http-kernel: ^6.4|^7.0
- symfony/routing: ^6.4|^7.0
Requires (Dev)
- guzzlehttp/psr7: ^2.4
- symfony/browser-kit: ^6.4|^7.0
- symfony/framework-bundle: ^6.4|^7.0
- symfony/panther: ^2.0
Suggests
- guzzlehttp/psr7: PSR-17 request factory
- symfony/panther: For End-to-End testing with a real Twirp Server and Client (see EndToEndTestCase).
This package is auto-updated.
Last update: 2025-04-20 22:51:46 UTC
README
A Symfony Bundle providing an implementation of a Twirp PHP Server.
Configuration
Autoloading
The classes generated by protoc-gen-twirp_symfony must be included in compsoer.json
{ "autoloading": { "psr-4": { "Zolex\\RPC\\": "twirp-php/src/Zolex/RPC/" } } }
Symfony Services
Additionally, the generated classes must be added as symfony services, for example via config/services.yml
services: Zolex\RPC\: resource: '../twirp-php/src/Zolex/RPC/'
Routing
To enable routing for the symfony bundle, simply add this files to your project config/routes/twirp.yaml
twirp: type: twirp resource: .
Static routes for each method in each twirp service are generated automatically.
You can view the available routes by typing bin/console debug:router
.
Service Prefix
A custom prefix should be defined for each service in the project's bundle config. The key in the services section must match the fully qualified classname of your service implementation.
config/packages/zolex_twirp.yaml
zolex_twirp: services: App\Twirp\ExampleService: prefix: '/examples.twirphp-server'
Generate the code
First you need to install protoc and the protoc-gen-twirp_symfony plugin and of course you need a protobuf service definition.
These are the aditional protoc arguments you have to specify for symfony.
--plugin=protoc-gen-twirp_symfony=/usr/local/bin/protoc-gen-twirp_symfony \ --php_out=path/to/twirp-php \ --twirp_symfony_out=path/to/twirp-php
NOTE: See autoloading/services section for
path/to/twirp-php
Implement a Service
The generatedd code from the twirp_symfony plugin includes Interfaces that you have to implement and register your implementation as a Symfony service (with symfony/framework-bundle just place it in your app folder, that's all it needs!)
class ExampleService implements ExampleServiceInterface { public function doSomething(array $ctx, InputMessage $input): OutputMessage { return new OutputMessage(['content' => 'It\'s that easy!']); } }
Of course your service is registered for Dependency Injection so just typehint any symfony service that you need.
Events
This bundle is designed with an Event Driven Architecture utilizing Symfony standards. We explicitly do no implement the hooks- and interceptors feature as described by Twirp. Instead of a Twirp ServerHook you are going to write EventListeners or EventSubscribers, hooking into the Symfony kernel events.
Every Service that generated by protoc-gen-twirp_symfony and that you implement in your project is automatically registered as a ServiceListener.
Inlcuded Listeners
Event | Priority | Listener | Task |
---|---|---|---|
kernel.request | 10 | ServiceListener | Creates an instance of a Twirp Input Message |
kernel.request | 6 | DeserializeListener | Deseriaizes the Rrequest Body into the Twirp Message |
kernel.view | 10 | ServiceListener | Handles the request by calling the Twirp Service Method |
kernel.view | 6 | SerializeListener | Serializes the output of the service method to JSON or protobuf |
kernel.exception | 100 | ExceptionListener | Converts Exceptions to TwirpErrors and sends the error response |
Utilizing the following constants makes it easy to hook in your custom listeners or subscribers at right place:
class EventPriorities { // kernel.request public const PRE_INPUT_FACTORY = 11; public const POST_INPUT_FACTORY = 9; public const PRE_DESERIALIZE = 7; public const POST_DESERIALIZE = 5; // kernel.view public const PRE_HANDLE_REQUEST = 11; public const POST_HANDLE_REQUEST = 9; public const PRE_SERIALIZE = 7; public const POST_SERIALIZE = 5; // kernel.terminate public const POST_SEND_RESPONSE = 0; }
Implement a custom listener or subscriber
On top of the Event there are plenty of options how you can determine if your listener should run. It's recommened to make the decision using the folowing request attributes and implement the Early Return Pattern.
input
: The twirp input message (when in the REQUEST event)_route
: The name of the called route (see routing section)_twirp_context
: The twirp context_twirp_prefix
: Twirp path prefix (see configuration section)_twirp_package
: Twirp package name (from the protobuf definition)_twirp_service
: Twirp service name (e.g. ExampleService)_twirp_method
: The twirp method to be calledd (e.g. doSomething)
Note: In the VIEW event you should use
$event->getControllerResult()
to retrieve the input message before the Reuqest was handled by ServiceListener and the output message afterwards.
Here is an example on how to inject a value to the twirp context for a specific twirp service method.
class AddToContextSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ KernelEvents::VIEW => ['onKernelView', EventPriorities::PRE_HANDLE_REQUEST] ]; } public function onKernelView(ViewEvent $event): void { $request = $event->getRequest(); $route = $request->attributes->get('_route'); if ('twirp:example_service:some_method' !== $route) { return; } $ctx = $request->attributes->get('_twirp_context'); $ctx['some_new_context'] = 1337; $request->attributes->set('_twirp_context', $ctx); }
Examples
Have a look at the example server project using this bundle.