folk-plugin-grpc 0.2.1

gRPC plugin for Folk — unary call passthrough to PHP workers via tonic
Documentation
# folk-plugin-grpc

gRPC plugin for Folk — accepts gRPC calls via tonic/HTTP2 and dispatches to PHP workers. No Rust protobuf codegen required — all protobuf handling happens in PHP.

## Configuration

Add to `folk.build.toml`:

```toml
[[plugin]]
crate_name = "folk-plugin-grpc"
version = "0.1"
config_key = "grpc"
```

Configure in `folk.toml`:

```toml
[grpc]
listen = "0.0.0.0:50051"
```

### Settings

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `listen` | `SocketAddr` | `"0.0.0.0:50051"` | Address and port to listen on for gRPC connections. |
| `reflection` | `bool` | `false` | *Planned.* gRPC server reflection (allows `grpcurl` without `-proto` flag). Not yet implemented. |

Full example:

```toml
[grpc]
listen = "0.0.0.0:50051"
```

Currently, clients must provide the `.proto` file for service discovery:

```bash
grpcurl -plaintext -proto proto/greeter.proto localhost:50051 list
grpcurl -plaintext -proto proto/greeter.proto localhost:50051 describe greeter.Greeter
```

## How it works

```
gRPC client ──HTTP/2──> folk-plugin-grpc (Rust)
                            ├── extracts service, method, payload, metadata from request
                            ├── encodes as MessagePack {service, method, payload, metadata}
                            └── dispatches to PHP worker via grpc.call RPC
                              PHP handler
                                    ├── decodes protobuf request (google/protobuf PHP)
                                    ├── executes business logic
                                    ├── encodes protobuf response
                                    └── returns raw bytes
                            folk-plugin-grpc
                                    └── wraps in gRPC frame, sends HTTP/2 response
```

The Rust plugin handles HTTP/2, gRPC framing, and metadata extraction.
PHP handles protobuf serialization and business logic.

### Metadata

gRPC metadata (the equivalent of HTTP headers) is extracted from the request and passed to PHP handlers via `Context`. Standard HTTP/2 and gRPC internal headers are filtered out — everything else is available:

```php
$ctx->getValue('authorization');  // "Bearer eyJ..."
$ctx->getValue('x-request-id');   // "abc-123"
$ctx->getMetadata();              // all metadata as array
```

## Usage with folk-sdk (plain PHP)

### 1. Define your proto

```protobuf
syntax = "proto3";
package greeter;

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

message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
```

### 2. Generate PHP classes

```bash
protoc --php_out=. proto/greeter.proto
```

This generates message classes (`HelloRequest`, `HelloReply`) but not service interfaces. You write the handler manually.

### 3. Write the handler (raw bytes mode)

```php
<?php

use Folk\Sdk\Grpc\GrpcModeHandler;
use Folk\Sdk\Grpc\Context;

class GreeterHandler implements GrpcModeHandler
{
    public function call(string $service, string $method, string $payload, Context $context): string
    {
        return match ($method) {
            'SayHello' => $this->sayHello($payload, $context),
            default => throw new \RuntimeException("Unknown method: {$method}"),
        };
    }

    private function sayHello(string $payload, Context $context): string
    {
        $request = new \Greeter\HelloRequest();
        $request->mergeFromString($payload);

        $token = $context->getValue('authorization'); // metadata access

        $reply = new \Greeter\HelloReply();
        $reply->setMessage("Hello, {$request->getName()}!");

        return $reply->serializeToString();
    }
}
```

### 4. Register

```php
$loop = new \Folk\Sdk\Worker\WorkerLoop();
$loop->registerGrpcHandler(new GreeterHandler());
$loop->run();
```

## Usage with Laravel (folk-laravel)

Laravel integration uses typed handlers with auto-detection of protobuf parameters.

### 1. Define your proto and generate PHP classes

```bash
protoc --php_out=app/Proto proto/greeter.proto
```

### 2. Create a service interface

```php
<?php
// app/Proto/Greeter/GreeterInterface.php

namespace App\Proto\Greeter;

use Folk\Sdk\Grpc\ServiceInterface;

interface GreeterInterface extends ServiceInterface
{
    public const NAME = "greeter.Greeter";

    public function SayHello(HelloRequest $in): HelloReply;
    public function SayGoodbye(HelloRequest $in): HelloReply;
}
```

The `NAME` constant must match the protobuf `package.Service` name — this is how the router maps incoming gRPC calls to your handler.

### 3. Implement the service

```php
<?php
// app/Grpc/GreeterService.php

namespace App\Grpc;

use App\Proto\Greeter\GreeterInterface;
use App\Proto\Greeter\HelloReply;
use App\Proto\Greeter\HelloRequest;

class GreeterService implements GreeterInterface
{
    public function SayHello(HelloRequest $in): HelloReply
    {
        $reply = new HelloReply();
        $reply->setMessage("Hello, {$in->getName()}!");
        return $reply;
    }

    public function SayGoodbye(HelloRequest $in): HelloReply
    {
        $reply = new HelloReply();
        $reply->setMessage("Goodbye, {$in->getName()}!");
        return $reply;
    }
}
```

The `GrpcRouter` auto-detects protobuf-typed parameters and handles `mergeFromString()` / `serializeToString()` automatically. You work with typed objects, not raw bytes.

### 4. Register in config

```php
// config/folk.php

return [
    'grpc' => [
        'services' => [
            \App\Proto\Greeter\GreeterInterface::class => \App\Grpc\GreeterService::class,
        ],
    ],
];
```

That's it. `FolkServiceProvider` reads the config, resolves services from the Laravel container, and registers them with the `GrpcRouter`.

### Accessing metadata in typed handlers

Add `Context` as the first parameter:

```php
use Folk\Sdk\Grpc\Context;

class GreeterService implements GreeterInterface
{
    public function SayHello(Context $ctx, HelloRequest $in): HelloReply
    {
        $token = $ctx->getValue('authorization');
        // ...
    }
}
```

The router detects `Context` parameters automatically and injects the metadata.

### Multiple services

Register as many services as you need:

```php
// config/folk.php

'grpc' => [
    'services' => [
        \App\Proto\Greeter\GreeterInterface::class => \App\Grpc\GreeterService::class,
        \App\Proto\Auth\AuthInterface::class       => \App\Grpc\AuthService::class,
        \App\Proto\Orders\OrdersInterface::class   => \App\Grpc\OrdersService::class,
    ],
],
```

### Compatibility with Spiral/RoadRunner

If you have services generated by `protoc-gen-php-grpc` (Spiral), they work with Folk:

```php
// Spiral-generated interface
interface InfoInterface extends \Spiral\RoadRunner\GRPC\ServiceInterface
{
    public const NAME = "myapp.Info";
    public function Get(\Spiral\RoadRunner\GRPC\ContextInterface $ctx, \Google\Protobuf\GPBEmpty $in): ServiceInfo;
}

// Register as-is — Folk injects its own Context for the $ctx parameter
'services' => [
    InfoInterface::class => InfoService::class,
],
```

The `NAME` constant is read from any interface the handler implements. Context parameters (regardless of type) receive Folk's `Context` instance.

## Testing with curl

```bash
# Encode HelloRequest { name: "Folk" } as protobuf
# Field 1 (string): 0x0a + length + bytes
printf '\x00\x00\x00\x00\x06\x0a\x04Folk' | curl --http2-prior-knowledge \
  -X POST http://localhost:50051/greeter.Greeter/SayHello \
  -H "content-type: application/grpc" \
  -H "authorization: Bearer my-token" \
  --data-binary @- -o response.bin

# Decode response (skip 5-byte gRPC frame)
python3 -c "
data = open('response.bin','rb').read()
proto = data[5:]
print(proto[2:2+proto[1]].decode())
"
# Output: Hello, Folk!
```

## Testing with grpcurl

```bash
# List services (requires -proto flag)
grpcurl -plaintext -proto proto/greeter.proto localhost:50051 list

# Describe a service
grpcurl -plaintext -proto proto/greeter.proto localhost:50051 describe greeter.Greeter

# Note: grpcurl RPC calls require HTTP/2 trailers support (in progress)
```

## Requirements

- PHP: `google/protobuf` package (`composer require google/protobuf`)
- `protoc` for generating PHP message classes

## License

MIT