Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Protobuf Laravel Package

protobuf-php/protobuf

Pure PHP implementation of Google Protocol Buffers for serializing structured data. Generate message classes, encode and decode efficiently, and integrate protobuf definitions into PHP projects with a familiar API.

Deep Wiki
Context7

Getting Started

Install (pin it — see Gotchas):

composer require protobuf-php/protobuf:^3.0
composer require --dev protobuf-php/protobuf-plugin:^3.0   # provides vendor/bin/protoc-gen-php

You also need the Google protoc compiler on the machine / in CI (this library is only the runtime; protoc does the parsing).

Look here first, in this order:

  1. vendor/protobuf-php/protobuf/src/Protobuf/Message.php — the base class every generated message extends. serialize(), mergeFrom(), readFrom(), fromStream(), hasX()/clearX() all come from here.
  2. vendor/protobuf-php/protobuf/src/Protobuf/Configuration.php — the singleton that holds extension registrations and memory/stream settings. Everything configurable lives here.
  3. One generated class, top to bottom. It is plain PHP with @Protobuf\Field docblock annotations on protected properties plus getters/setters. Five minutes of reading makes the rest of the library obvious.
  4. Protobuf\Stream — the wrapper used for binary I/O; this is what you touch when payloads get big.

Generate classes:

protoc --proto_path=proto \
  --plugin=protoc-gen-php=vendor/bin/protoc-gen-php \
  --php_out=src/Proto \
  proto/events.proto

Then map the generated namespace to the directory in composer.json ("Acme\\Proto\\": "src/Proto/"). If the plugin emits an unexpected namespace, that mapping is configured via the plugin's config file — run protoc-gen-php --help and check the README for the protobuf.ini / namespace-mapping section; the default is derived from the proto package plus the file name.

First use case — an event on a queue:

use Acme\Proto\Event;

$event = new Event();
$event->setId('evt_123');
$event->setType('order.created');
$event->setPayload($json);

$bytes = $event->serialize();                    // wire-format string
$stream = $event->toStream();                    // Protobuf\Stream, for large bodies

// consumer
$event = Event::fromStream(Protobuf\Stream::fromString($bytes));
// (older/newer 3.x builds expose Event::fromBinary($bytes) — check the base class)
if ($event->hasPayload()) {
    echo $event->getPayload();
}

Round-trip that once in a test before you build anything on top of it. It is the cheapest possible smoke test of generator + runtime + autoload wiring all at once.

Implementation Patterns

Keep generated classes at the boundary, not in the domain. Generated classes are a wire format, not a model: they are mutable, use null/has* semantics, and their API changes whenever someone edits the .proto. Put a thin hand-written mapper (EventMapper::toDomain()/fromDomain()) between them and the rest of the app. This is the single decision that makes the package tolerable long-term.

Make generation a build step, not a manual chore.

proto:
	protoc --proto_path=proto --plugin=protoc-gen-php=vendor/bin/protoc-gen-php \
	  --php_out=src/Proto proto/*.proto

Commit the generated output (it's reviewable, and it lets deploys work without protoc), and add a CI job that regenerates into a temp dir and fails on git diff — otherwise the PHP classes silently drift from the .proto.

Schema evolution rules you must actually enforce. Proto2's guarantees only hold if you obey them: never renumber a field, never change a field's type, never reuse a tag from a deleted field (use reserved), and treat required as forbidden in anything that crosses a network boundary (a missing required field makes deserialization throw — a producer adding a field can take down an old consumer). Prefer optional + has*() checks everywhere.

Repeated and nested fields are Protobuf\Collection objects, not arrays:

foreach ($order->getItems() as $item) { ... }     // Collection is iterable/Countable
$order->addItems(new Item());                      // singular-named adder
$order->setItems($collection);                     // wholesale replace

Don't try $order->getItems()[0] — it isn't an array and doesn't implement ArrayAccess in all versions.

Use mergeFrom / streams for large payloads. fromStream() + mergeFrom() read incrementally instead of materializing the whole byte string, which matters because this is pure PHP and memory doubles easily:

$msg = new Event();
$msg->mergeFrom(Protobuf\Stream::fromString($chunk));   // accumulate across chunks

Register one Configuration in your DI container and pass it consistently. Extensions, memory limits, and any custom settings live there; a message decoded with a different configuration than the one it was encoded with is a subtle class of bug. Generated constructors/static factories accept it as a trailing argument — thread it through your repository/consumer code once and never call Configuration::getDefault() ad hoc from deep inside business logic.

Extensions for per-team metadata. Rather than editing a shared .proto for every team that wants an extra field, define extend blocks and register them on the shared Configuration at boot. You get additive, independently versioned fields without touching the base message.

JSON is your problem. There is no canonical proto3-style JSON mapping here and the messages aren't JsonSerializable. If you need JSON on an HTTP edge, write a Symfony Serializer normalizer (or a small recursive mapper keyed off the @Protobuf\Field annotations) so serialization lives in one class instead of being copy-pasted. json_encode($message) returns {} because all the state is in protected properties.

Testing that pays for itself:

  • Property/round-trip test: random message → serialize() → decode → assert equality.
  • Golden fixtures generated with protoc --encode=acme.Event proto/events.proto < fixture.json > fixture.bin, decoded by PHP. This is your only real protection against a wire-format regression and against cross-language drift.
  • A two-version interop test (old consumer, new producer) to verify unknown-field handling — see Gotchas.

Gotchas and Tips

It is unmaintained (last release 2016) and proto2-only. No syntax = "proto3", no map<>, no well-known types (Timestamp, Any, Duration), no JSON mapping. If your peers are on proto3 or gRPC, stop here and use google/protobuf + grpc/grpc instead — the interop gap will bite you on day one. If you're staying, pin the exact version in composer.json and treat upgrades as a project, not a composer update.

PHP 8 compatibility is not guaranteed. The library targets PHP 5.5-era syntax; verify on your actual PHP version before committing, and run the full test suite under the oldest PHP you deploy. Have a fork/vendor-patch plan; several exist, and it's usually a handful of small fixes.

opcache.save_comments=0 will break everything. Field metadata is read from docblock annotations on the generated properties. Disabling comments in production opcache makes every message decode as an empty shell with no error — an extremely confusing failure mode. Keep comments on in prod, and add it to your deployment checklist.

json_encode / get_object_vars / (array) give you nothing — properties are protected. Always go through the generated getters or a normalizer.

Unknown fields and forwarding. If your service decodes a message and re-encodes it, any field added by a newer producer is at risk of being dropped. The library does track unknown fields in some versions — do not assume it does. Write the two-version test described above before you put this on a message-forwarding path.

64-bit integers. On 64-bit PHP, int64/uint64 values outside PHP_INT_MAX surface as strings; on 32-bit they may need special handling. Don't (int) cast them, and don't put them in an int-typed domain property.

Debugging binary payloads. Never eyeball hex. Use protoc:

protoc --decode_raw < payload.bin                          # structure without a schema
protoc -I proto --decode=acme.Event proto/events.proto < payload.bin

Wrap deserialization in try/catch at the trust boundary and convert to a 400/bad-message, not a 500: malformed input throws Protobuf\Exception\* (invalid wire type, truncated varint, missing required field). A single poisoned queue message should not crash your worker loop.

Performance. Pure-PHP encode/decode is roughly an order of magnitude slower than the C extension. Benchmark your hot path early; if it's the bottleneck, cache the encoded bytes rather than decoded objects (unserializing the generated objects is both slower and larger), and avoid constructing messages inside tight loops just to read one field.

Extension registration must happen before decode, in a bootstrap that runs for every entry point (HTTP, CLI, queue worker). A missing registration doesn't error loudly — you just silently lose the extension data.

Watch the namespace mapping. "Class not found" after generating almost always means the proto package didn't map to the PSR-4 prefix you expected. Inspect the first ten lines of a generated file; the namespace statement is the ground truth, and composer dump-autoload won't fix a mismatch.

Keep the generator version in lockstep with the runtime version. Mixing a 3.x plugin with a 3.x runtime from different commits produces messages with fields the runtime doesn't know how to annotate.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource