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

Avro Laravel Package

apache/avro

Apache Avro for PHP: a schema-based data serialization framework with compact binary encoding, rich schema evolution, object container files, and RPC support. Use it to exchange structured data reliably between services and languages while keeping payloads small.

View on GitHub
Deep Wiki
Context7

Getting Started

Apache Avro is not a Laravel package — there is no service provider, facade, config file, or published assets. It is a low-level serialization library with a global Avro* class set (AvroSchema, AvroDatumWriter, AvroDatumReader, AvroStringIO, AvroDataIO). You wire it into Laravel yourself.

Install and verify the PHP implementation is actually present:

composer require apache/avro
composer show apache/avro   # confirm version + PHP constraint
php -r "var_dump(class_exists('AvroSchema'));"

Look first at three entry points:

  • AvroSchema::parse($json) — compiles an .avsc schema. Reuse the parsed object; parsing is the expensive part.
  • AvroDatumWriter / AvroDatumReader — binary encode/decode of a single datum over an AvroIO stream.
  • AvroDataIO::open_file($path, $mode, $schema, $codec) — Avro Object Container Files (the .avro files with embedded schema and codec).

First use case: encode a domain event into a binary payload you can push onto a queue, Kafka topic, or object storage.

use AvroSchema;
use AvroDatumWriter;
use AvroStringIO;

$schema = AvroSchema::parse(file_get_contents(base_path('schemas/order_created.avsc')));

$io = new AvroStringIO();
(new AvroDatumWriter($schema))->write([
    'order_id' => $order->id,
    'total_cents' => $order->total_cents,
    'currency' => 'EUR',
    'created_at' => $order->created_at->getTimestampMs(),
    'notes' => $order->notes, // null union
], $io);

$payload = $io->string();

Decode by rewinding before reading:

use AvroDatumReader;

$io = new AvroStringIO($payload);
$io->seek(0);
$record = (new AvroDatumReader($schema))->read($io);

Bind it as a singleton so the schema is parsed once per request/worker:

$this->app->singleton(OrderEventCodec::class, fn () => new OrderEventCodec(
    AvroSchema::parse(file_get_contents(base_path('schemas/order_created.avsc')))
));

Implementation Patterns

Dedicated codec class per schema. Don't scatter AvroSchema::parse through controllers and jobs. Wrap encode/decode, and validate the record shape before it reaches Avro so you get a useful error instead of AvroTypeException.

final class AvroCodec
{
    public function __construct(private \AvroSchema $schema) {}

    public function encode(array $record): string
    {
        $io = new \AvroStringIO();
        (new \AvroDatumWriter($this->schema))->write($record, $io);
        return $io->string();
    }

    public function decode(string $payload): array
    {
        $io = new \AvroStringIO($payload);
        $io->seek(0);
        return (new \AvroDatumReader($this->schema))->read($io);
    }
}

Object Container Files for archival / data lake dumps. Use AvroDataIO::open_file with an explicit codec (deflate is the practical choice; it needs ext-zlib). Files are append-only batches — good for nightly exports, bad for random updates.

$fh = \AvroDataIO::open_file(storage_path("app/avro/orders-{$date}.avro"), 'w', $schemaJson, 'deflate');
foreach ($orders->lazyById(1000) as $order) {
    $fh->append($order->toAvroRecord());
}
$fh->close();

Reading back is a generator/array depending on version — always iterate, never assume the whole file fits in memory on a worker.

Schema evolution: pass both schemas. The reader supports writer/reader schema resolution, which is how you roll forward without rewriting old files:

$reader = new \AvroDatumReader($writerSchema, $readerSchema);

Keep .avsc files in resources/schemas/ under version control, with a v1/, v2/ naming convention and additive-only changes (new fields with defaults, never reordered or renamed fields).

Schema registry integration. Avro ships no registry client. If you're on Confluent/Kafka, prepend the magic byte + 4-byte schema ID yourself and cache the parsed schema by ID. The registry lookup should be a Cache::remember in a SchemaRegistry service, not a call inside the producer loop.

Mapper layer between Eloquent and Avro. Avro records are flat-ish typed arrays. Add toAvroRecord() methods or dedicated Data Transfer Objects rather than passing $model->toArray() — column name drift and Carbon objects will break encoding in ways that are hard to trace. Encode timestamps as long (ms) consistently and document the unit in the schema.

Queue integration. For a ShouldQueue event or a job payload, keep Avro binary out of the JSON-serialized job properties. Instead, encode inside handle() and hand the binary to your transport (Kafka producer, S3 PUT, raw HTTP body). Never serialize() a binary Avro blob into a Laravel queued job.

Testing pattern. Round-trip is the only test that matters: encode → decode → assert equality. Freeze schema fixtures in tests/fixtures/avsc/ and add a test that asserts each .avsc parses, so CI catches malformed schemas before deploy.

$decoded = $codec->decode($codec->encode($record));
$this->assertSame($record, $decoded);

Validation rule. If you accept Avro payloads at an endpoint (webhook, consumer), wrap decode in a try/catch for AvroException and return 422; do not let raw library exceptions leak into HTTP responses.

Gotchas and Tips

  • No Laravel integration out of the box. No config, no facade, no Avro:: alias. Everything is manual container binding and manual path resolution. Budget for a thin wrapper package of your own.
  • Global, non-namespaced classes. AvroSchema etc. live at the root namespace. Older releases require an explicit require_once on the implementation entry file rather than relying on Composer autoload — verify with class_exists('AvroSchema') after composer dump-autoload.
  • Strict typing kills convenience. Avro does not coerce. "42" for an int field fails; null is only valid where a union explicitly includes "null"; enum symbols must be exact strings present in symbols. Normalize in your mapper, not by hoping the writer is lenient.
  • Union ordering and defaults. The default value must match the first branch of a union. ["null", "string"] with "default": "x" is invalid. ["string", "null"] with "default": null is invalid. This is the most common schema parse failure.
  • No PHP int/long distinction. PHP ints are platform-sized; writing a value beyond int range into an Avro int (32-bit) field throws. Use long for IDs and timestamps.
  • Logical types are the weak spot. Decimal, date, timestamp-millis/micros, uuid, and duration are not fully handled by the PHP implementation. Decimal arrives as raw bytes plus scale — you must convert to/from a string or BCMath value manually. Timestamps are plain ints; the library will not give you a Carbon. Wrap conversions in your codec and test them explicitly.
  • Codec support. null and deflate are reliable; snappy typically isn't available in PHP. Files written with an unsupported codec will fail on read with an opaque error — record the codec in file metadata and check it.
  • Performance. This is pure PHP with schema traversal per record. Tens of thousands of records/sec is not realistic. Batch writes, avoid re-parsing schemas per item, and consider a sidecar (Java/Go) if throughput is the actual requirement. Profile before assuming Laravel is the bottleneck.
  • Memory on reads. AvroDataIO::open_file(...)->data() may load or eagerly chunk depending on version. For large files, iterate and use chunked exports; watch memory_get_peak_usage() in a test with a realistic file.
  • Error messages are terse. AvroTypeException / AvroSchemaParseException give you a field path at best. When debugging, json_encode($record) next to $schema->to_schema_json() (or the raw .avsc) in the exception message saves hours.
  • Long-lived workers and Octane. Schema objects are safe to cache in singletons; AvroStringIO instances are not — never share a stream across requests or coroutines. Create a fresh AvroStringIO per encode/decode.
  • Schema evolution rules. Additive fields only, with defaults. Renaming requires aliases, which the PHP implementation supports unevenly — test resolution across old files before committing to a rename.
  • Extension points are minimal. There is no plugin system, no custom type registration, no code generation from .avsc to PHP classes. If you need generated DTOs, expect to write them (or a small generator) yourself.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo