PHPackages                             cleatsquad/php-grpc-frame-codec - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. cleatsquad/php-grpc-frame-codec

ActiveLibrary

cleatsquad/php-grpc-frame-codec
===============================

PHP codec for the standard 5-byte gRPC message frame used by franken-grpc-style relays — runtime-agnostic, no dependency on FrankenPHP.

v2.1.0(today)05↑2900%[1 PRs](https://github.com/CleatSquad/php-grpc-frame-codec/pulls)MITPHPPHP &gt;=8.1CI passing

Since Aug 28Pushed todayCompare

[ Source](https://github.com/CleatSquad/php-grpc-frame-codec)[ Packagist](https://packagist.org/packages/cleatsquad/php-grpc-frame-codec)[ Docs](https://github.com/CleatSquad/php-grpc-frame-codec)[ RSS](/packages/cleatsquad-php-grpc-frame-codec/feed)WikiDiscussions main Synced today

READMEChangelog (4)Dependencies (2)Versions (5)Used By (0)

php-grpc-frame-codec
====================

[](#php-grpc-frame-codec)

[![Tests](https://github.com/CleatSquad/php-grpc-frame-codec/actions/workflows/tests.yml/badge.svg)](https://github.com/CleatSquad/php-grpc-frame-codec/actions/workflows/tests.yml)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)

The 5-byte gRPC message frame your PHP backend must send back to [`franken-grpc`](https://github.com/CleatSquad/franken-grpc) — extracted into a tiny, dependency-free package so every backend integrating with the relay doesn't have to hand-roll it.

**Not tied to FrankenPHP.** Despite the name of the relay it was built for, this package is pure byte manipulation (`pack()`/`unpack()`) with no runtime dependency at all. It works identically under nginx+PHP-FPM, Apache+mod\_php, RoadRunner, Swoole, plain php-fpm, or FrankenPHP — the only thing that matters is that *something* in front of your backend speaks the same 5-byte frame (franken-grpc does; anything reusing its wire contract would too).

Why this exists
---------------

[](#why-this-exists)

franken-grpc's contract is asymmetric: the **request** your backend receives is raw protobuf, no envelope (the relay already stripped it converting the incoming HTTP/2 gRPC call into a plain HTTP/1.1 POST) — but the **response** you send back must carry the standard gRPC frame:

```
1 byte   compression flag — must be 0, franken-grpc does not decompress
4 bytes  big-endian payload length
N bytes  raw protobuf payload

```

Getting this wrong produces no clean error on either side — just a `frame declares compression flag N, compressed frames are not supported by this relay` from the relay, once you happen to send the request-side framing back by mistake. This package exists so that byte-level detail is written once, tested, and reused instead of copy-pasted per project.

Install
-------

[](#install)

```
composer require cleatsquad/php-grpc-frame-codec
```

Usage
-----

[](#usage)

```
use CleatSquad\GrpcFrameCodec\GrpcFrameCodec;

$codec = new GrpcFrameCodec();

// Your backend receives the raw protobuf request body directly — no
// decode() needed on the way in.
$requestBytes = file_get_contents('php://input');

// ... decode $requestBytes with your generated protobuf message class,
// handle the call, encode the response message ...

// Frame the response before sending it back:
header('Content-Type: ' . GrpcFrameCodec::CONTENT_TYPE);
echo $codec->encode($responseBytes);
```

`decode()` is provided for symmetry and for tests — most backends never need to call it, since franken-grpc already strips the frame on the request side.

Server-streaming responses
--------------------------

[](#server-streaming-responses)

A server-streaming RPC emits several frames back-to-back in the same HTTP response. If you're reading the body incrementally (chunked output, `fwrite`/flush per message, or a client consuming the response as a stream) you may not have a full frame yet on a given read. `readNextFrame()`handles that: it consumes one frame from the front of `$buffer` and advances it past what it read, returning `null` — without touching `$buffer` — when the frame isn't complete yet.

```
use CleatSquad\GrpcFrameCodec\GrpcFrameCodec;

$codec = new GrpcFrameCodec();
$buffer = '';

foreach ($chunksAsTheyArrive as $chunk) {
    $buffer .= $chunk;

    while (($payload = $codec->readNextFrame($buffer)) !== null) {
        handle($payload); // one decoded message
    }
}
```

For a response you already have in full (the common unary case), `decode()` remains the simpler one-shot call.

Examples by PHP runtime
-----------------------

[](#examples-by-php-runtime)

The snippet above works as-is under any of these — only the entry point changes.

### nginx + PHP-FPM

[](#nginx--php-fpm)

```
// public/index.php
// nginx forwards POST /{package}.{Service}/{Method} to this script via fastcgi_pass
use CleatSquad\GrpcFrameCodec\GrpcFrameCodec;

$codec = new GrpcFrameCodec();
$requestBytes = file_get_contents('php://input');

$responseBytes = handle($_SERVER['REQUEST_URI'], $requestBytes); // your dispatch + business logic

header('Content-Type: application/grpc+proto');
echo $codec->encode($responseBytes);
```

### Apache + mod\_php

[](#apache--mod_php)

Identical to the nginx example — `mod_php` is just another SAPI calling the same `index.php`. Route `/{package}.{Service}/{Method}` to it with a `RewriteRule`, the same way you would for any front-controller pattern.

### RoadRunner

[](#roadrunner)

```
use CleatSquad\GrpcFrameCodec\GrpcFrameCodec;
use Spiral\RoadRunner\Http\PSR7Worker;

$codec = new GrpcFrameCodec();

while ($request = $psr7Worker->waitRequest()) {
    $requestBytes = (string) $request->getBody();
    $responseBytes = handle($request->getUri()->getPath(), $requestBytes);

    $response = new \Nyholm\Psr7\Response(200, ['Content-Type' => 'application/grpc+proto'], $codec->encode($responseBytes));
    $psr7Worker->respond($response);
}
```

### FrankenPHP

[](#frankenphp)

```
// public/index.php — same code as the nginx+PHP-FPM example, FrankenPHP
// is just the server process running it (worker mode or classic).
```

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

0d ago

Major Versions

v1.0.1 → v2.0.02026-08-28

### Community

Maintainers

![](https://www.gravatar.com/avatar/75b8cb31786be9b4017a0c617eebe3a0cd3b8d039069ffad5bb5007b5510fd9d?d=identicon)[mimou78](/maintainers/mimou78)

---

Top Contributors

[![mohaelmrabet](https://avatars.githubusercontent.com/u/3817628?v=4)](https://github.com/mohaelmrabet "mohaelmrabet (5 commits)")

---

Tags

gRPCprotobufwire-formatfranken-grpc

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/cleatsquad-php-grpc-frame-codec/health.svg)

```
[![Health](https://phpackages.com/badges/cleatsquad-php-grpc-frame-codec/health.svg)](https://phpackages.com/packages/cleatsquad-php-grpc-frame-codec)
```

###  Alternatives

[protobuf-php/protobuf

PHP implementation of Google's Protocol Buffers

2651.5M22](/packages/protobuf-php-protobuf)[spiral/roadrunner-laravel

Laravel integration for RoadRunner with support for HTTP, Jobs, gRPC, and Temporal plugins - going beyond Octane's capabilities

507336.1k3](/packages/spiral-roadrunner-laravel)[twirp/twirp

PHP port of Twitch's Twirp RPC framework

1581.3M5](/packages/twirp-twirp)[roadrunner-php/laravel-bridge

Laravel integration for RoadRunner with support for HTTP, Jobs, gRPC, and Temporal plugins - going beyond Octane's capabilities

50469.7k1](/packages/roadrunner-php-laravel-bridge)[nuwber/rabbitevents

The Nuwber RabbitEvents package

119535.0k4](/packages/nuwber-rabbitevents)[centraldesktop/protobuf-php

PHP implementation of Google's Protocol Buffers

27730.4k8](/packages/centraldesktop-protobuf-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
