MCPcopy Index your code
hub / github.com/dunglas/frankenphp-grpc

github.com/dunglas/frankenphp-grpc @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
48 symbols 82 edges 5 files 12 documented · 25%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

FrankenPHP gRPC Server

A FrankenPHP extension that allows you to run a gRPC server triggering code written either in PHP or Go. Under the hood, this extension uses the gRPC for Go library and FrankenPHP's Go extension support.

[!WARNING]

This extension is highly experimental and not recommended for production use. The public API may change at any time without notice.

Features

  • Run a high performance gRPC server with FrankenPHP (the PHP part is executed in a worker loop)
  • Write gRPC service handlers in PHP
  • Write gRPC service handlers in Go
  • Write gRPC service handlers in a mix of PHP and Go 🤯
  • All features supported by the gRPC for Go library
  • Entirely written in Go, no C code!
  • API Platform compatibility!

Prerequisites

  • FrankenPHP extensions prerequisites: https://frankenphp.dev/docs/extensions/#prerequisites
  • gRPC for Go prerequisites: https://grpc.io/docs/languages/go/quickstart/#prerequisites

Usage

Create a Go module

go mod init example.com/mygrpcserver 

Create a Protobuf Definition:

Create a .proto file describing your gRPC service and messages.

Example (in a helloworld/helloworld.proto file):

syntax = "proto3";

option go_package = "example.com/mygrpcserver/helloworld";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Generate the Go code:

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto

Implement the gRPC Server in Go

package mygrpcserver

import (
    "context"
    "fmt"

    pb "example.com/mygrpcserver/helloworld"
    "github.com/dunglas/frankenphp"
    phpGrpc "github.com/dunglas/frankenphp-grpc"
    "github.com/go-viper/mapstructure/v2"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

func init() {
    phpGrpc.RegisterGrpcServerFactory(func() *grpc.Server {
        s := grpc.NewServer()
        pb.RegisterGreeterServer(s, &server{})
        reflection.Register(s)

        return s
    })
}

type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    if in.Name == "" {
        return nil, fmt.Errorf("the Name field is required")
    }

    // Convert the request to a map[string]any
    var phpRequest map[string]any
    if err := mapstructure.Decode(in, &phpRequest); err != nil {
        return nil, err
    }

    // Call the PHP code, pass the map as a PHP associative array
    phpResponse, err := phpGrpc.HandleRequest(ctx, phpRequest)
        if err != nil {
        return nil, err
    }

    // Convert the PHP response (a map) back to a HelloReply struct
    var response pb.HelloReply
    if err := mapstructure.Decode(phpResponse.(frankenphp.AssociativeArray[any]).Map, &response); err != nil {
        return nil, err
    }

    return &response, nil
}

Refer to the gRPC for Go documentation for more details on how to implement your gRPC service in Go. Rfer to the FrankenPHP extensions documentation for more details on how to pass data from Go to PHP and vice versa.

Implement the gRPC Service Handler in PHP

Create a file named grpc-worker.php in the same directory as the FrankenPHP binary we'll build later:

<?php

// Require the Composer autoloader here if needed (API Platform, Symfony, etc.)
//require __DIR__ . '/vendor/autoload.php';

// Handler outside the loop for better performance (doing less work)
$handler = static function (array $request): array  {
    // Do something with the gRPC request

    return ['message' => "Hello, {$request['Name']}"];
};

$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
    $keepRunning = \frankenphp_handle_request($handler);

    // Call the garbage collector to reduce the chances of it being triggered in the middle of the handling of a request
    gc_collect_cycles();

    if (!$keepRunning) {
      break;
    }
}

Create the Caddyfile

Create a Caddyfile in the same directory as the FrankenPHP binary we'll build later:

{
    frankenphp
    grpc {
        address :50051 # Optional
        worker grpc-worker.php # Optional
        min_threads 50 # Optional, defaults to runtime.NumCPU()
    }
}

Build and Run the FrankenPHP Binary with the gRPC Extension

Run the server:

XCADDY_DEBUG=1
    CGO_ENABLED=1 \
    CGO_CFLAGS="$(php-config --includes) -I/opt/homebrew/include/" \
    CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs) -L/opt/homebrew/lib/ -L/usr/lib" \
    xcaddy build

./caddy run

Your gRPC server should now be running on localhost:50051.

We recommend using gRPC UI (a Postman-like GUI for gRPC) to test your server.

Credits

Created by Kévin Dunglas and sponsored by Les-Tilleuls.coop.

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

UnmarshalCaddyfile
called by 1
grpc.go
init
called by 0
grpc.go
RegisterGrpcServerFactory
called by 0
grpc.go
HandleRequest
called by 0
grpc.go
CaddyModule
called by 0
grpc.go
Provision
called by 0
grpc.go
Start
called by 0
grpc.go
Stop
called by 0
grpc.go

Shape

Method 26
Function 12
Struct 7
Interface 3

Languages

Go100%

Modules by API surface

testdata/helloworld.pb.go18 symbols
testdata/helloworld_grpc.pb.go16 symbols
grpc.go10 symbols
testdata/main.go4 symbols

For agents

$ claude mcp add frankenphp-grpc \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact