All Projects → flix-tech → avro-serde-php

flix-tech / avro-serde-php

Licence: MIT license
Avro Serialisation/Deserialisation (SerDe) library for PHP 7.3+ & 8.0 with a Symfony Serializer integration

Programming Languages

PHP
23972 projects - #3 most used programming language
shell
77523 projects
Makefile
30231 projects
Dockerfile
14818 projects

Projects that are alternatives of or similar to avro-serde-php

AvroConvert
Apache Avro serializer for .NET
Stars: ✭ 44 (+2.33%)
Mutual labels:  serialization, avro, deserialization, avro-format
avrow
Avrow is a pure Rust implementation of the avro specification https://avro.apache.org/docs/current/spec.html with Serde support.
Stars: ✭ 27 (-37.21%)
Mutual labels:  serialization, avro, deserialization, avro-schema
schema-registry-php-client
A PHP 7.3+ API client for the Confluent Schema Registry REST API based on Guzzle 6 - http://docs.confluent.io/current/schema-registry/docs/index.html
Stars: ✭ 40 (-6.98%)
Mutual labels:  avro, confluent, avro-schema
avrora
A convenient Elixir library to work with Avro schemas and Confluent® Schema Registry
Stars: ✭ 59 (+37.21%)
Mutual labels:  avro, confluent, avro-schema
Noproto
Flexible, Fast & Compact Serialization with RPC
Stars: ✭ 138 (+220.93%)
Mutual labels:  serialization, avro, deserialization
har-rs
A HTTP Archive format (HAR) serialization & deserialization library, written in Rust.
Stars: ✭ 25 (-41.86%)
Mutual labels:  serialization, serde, deserialization
Schema Registry
Confluent Schema Registry for Kafka
Stars: ✭ 1,647 (+3730.23%)
Mutual labels:  avro, confluent, avro-schema
serde
🚝 (unmaintained) A framework for defining, serializing, deserializing, and validating data structures
Stars: ✭ 49 (+13.95%)
Mutual labels:  serialization, serde, deserialization
kafka-protobuf-serde
Serializer/Deserializer for Kafka to serialize/deserialize Protocol Buffers messages
Stars: ✭ 52 (+20.93%)
Mutual labels:  serialization, serde, deserialization
Marshmallow
A lightweight library for converting complex objects to and from simple Python datatypes.
Stars: ✭ 5,857 (+13520.93%)
Mutual labels:  serialization, serde, deserialization
Typical
Typical: Fast, simple, & correct data-validation using Python 3 typing.
Stars: ✭ 111 (+158.14%)
Mutual labels:  serialization, serde, deserialization
Yyjson
The fastest JSON library in C
Stars: ✭ 1,894 (+4304.65%)
Mutual labels:  serialization, deserialization
Jsonapi Rails
Rails gem for fast jsonapi-compliant APIs.
Stars: ✭ 242 (+462.79%)
Mutual labels:  serialization, deserialization
Awesome Python Models
A curated list of awesome Python libraries, which implement models, schemas, serializers/deserializers, ODM's/ORM's, Active Records or similar patterns.
Stars: ✭ 124 (+188.37%)
Mutual labels:  serialization, deserialization
Lora Serialization
LoraWAN serialization/deserialization library for The Things Network
Stars: ✭ 120 (+179.07%)
Mutual labels:  serialization, deserialization
Yaxlib
Yet Another XML Serialization Library for the .NET Framework and .NET Core
Stars: ✭ 124 (+188.37%)
Mutual labels:  serialization, deserialization
Borer
Efficient CBOR and JSON (de)serialization in Scala
Stars: ✭ 131 (+204.65%)
Mutual labels:  serialization, deserialization
Dataclass factory
Modern way to convert python dataclasses or other objects to and from more common types like dicts or json-like structures
Stars: ✭ 116 (+169.77%)
Mutual labels:  serialization, deserialization
Pyjson tricks
Extra features for Python's JSON: comments, order, numpy, pandas, datetimes, and many more! Simple but customizable.
Stars: ✭ 131 (+204.65%)
Mutual labels:  serialization, deserialization
Flatsharp
Fast, idiomatic C# implementation of Flatbuffers
Stars: ✭ 133 (+209.3%)
Mutual labels:  serialization, deserialization

Avro SerDe for PHP 7.3+ and 8.0

php-confluent-serde Actions Status Maintainability Test Coverage Latest Stable Version Total Downloads License

Motivation

When serializing and deserializing messages using the Avro serialization format, especially when integrating with the Confluent Platform, you want to make sure that schemas are evolved in a way that downstream consumers are not affected.

Hence Confluent developed the Schema Registry which has the responsibility to validate a given schema evolution against a configurable compatibility policy.

Unfortunately Confluent is not providing an official Avro SerDe package for PHP. This library aims to provide an Avro SerDe library for PHP that implements the Confluent wire format and integrates FlixTech's Schema Registry Client.

Installation

This library is using the composer package manager for PHP.

composer require 'flix-tech/avro-serde-php:^1.6'

Quickstart

NOTE

You should always use a cached schema registry client, since otherwise you'd make an HTTP request for every message serialized or deserialized.

1. Create a cached Schema Registry client

See the Schema Registry client documentation on caching for more detailed information.

<?php

use FlixTech\SchemaRegistryApi\Registry\Cache\AvroObjectCacheAdapter;
use FlixTech\SchemaRegistryApi\Registry\CachedRegistry;
use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry;
use GuzzleHttp\Client;

$schemaRegistryClient = new CachedRegistry(
    new PromisingRegistry(
        new Client(['base_uri' => 'registry.example.com'])
    ),
    new AvroObjectCacheAdapter()
);

2. Build the RecordSerializer instance

The RecordSerializer is the main way you interact with this library. It provides the encodeRecord and decodeMessage methods for SerDe operations.

<?php

use FlixTech\AvroSerializer\Objects\RecordSerializer;

/** @var \FlixTech\SchemaRegistryApi\Registry $schemaRegistry */
$recordSerializer = new RecordSerializer(
    $schemaRegistry,
    [
        // If you want to auto-register missing schemas set this to true
        RecordSerializer::OPTION_REGISTER_MISSING_SCHEMAS => false,
        // If you want to auto-register missing subjects set this to true
        RecordSerializer::OPTION_REGISTER_MISSING_SUBJECTS => false,
    ]
);

3. Encoding records

This is a simple example on how you can use the RecordSerializer to encode messages in the Confluent Avro wire format.

<?php

/** @var \FlixTech\AvroSerializer\Objects\RecordSerializer $recordSerializer */
$subject = 'my-topic-value';
$avroSchema = AvroSchema::parse('{"type": "string"}');
$record = 'Test message';

$encodedBinaryAvro = $recordSerializer->encodeRecord($subject, $avroSchema, $record);
// Send this over the wire...

4. Decoding messages

This is a simple example on how you can use the RecordSerializer to decode messages.

<?php

/** @var \FlixTech\AvroSerializer\Objects\RecordSerializer $recordSerializer */
/** @var string $encodedBinaryAvro */
$record = $recordSerializer->decodeMessage($encodedBinaryAvro);

echo $record; // 'Test message'

Schema Resolvers

Schema Resolvers are responsible to know which Avro schema belongs to which type of record. This is especially useful if you want to manage your Avro schemas in separate files. Schema Resolvers enable you to integrate with whatever schema management concept you may have outside of the scope of this library.

Schema Resolvers take a $record of any type and try to resolve a matching AvroSchema instance for it.

FileResolver

In even moderately complicated applications you want to manage your schemas within the VCS, most probably as .avsc files. These files contain JSON that is describing the Avro schema.

The resolver takes a $baseDir in which you want to manage the files and an inflector callable, which is a simple function that takes the record as first parameter, and a second boolean $isKey parameter indicating if the inflection is targeting a key schema.

<?php

namespace MyNamespace;

use FlixTech\AvroSerializer\Objects\SchemaResolvers\FileResolver;
use function get_class;use function is_object;
use function str_replace;

class MyRecord {}

$record = new MyRecord();

$baseDir = __DIR__ . '/files';

$inflector = static function ($record, bool $isKey) {
    $ext = $isKey ? '.key.avsc' : '.avsc';
    $fileName = is_object($record)
        ? str_replace('\\', '.', get_class($record))
        : 'default';
    
    return $fileName . $ext;
};


echo $inflector($record, false); // MyNamespace.MyRecord.avsc
echo $inflector($record, true); // MyNamespace.MyRecord.key.avsc

$resolver = new FileResolver($baseDir, $inflector);

$resolver->valueSchemaFor($record); // This will load from $baseDir . '/' . MyNamespace.MyRecord.avsc
$resolver->keySchemaFor($record); // This will load from $baseDir . '/' . MyNamespace.MyRecord.key.avsc

CallableResolver

This is the simplest but also most flexible resolver. It just takes two callables that are responsible to fetch either value- or key-schemas respectively. A key schema resolver is optional.

<?php

use FlixTech\AvroSerializer\Objects\SchemaResolvers\CallableResolver;
use PHPUnit\Framework\Assert;
use function Widmogrod\Functional\constt;

$valueSchemaJson = '
{
  "type": "record",
  "name": "user",
  "fields": [
    {"name": "name", "type": "string"},
    {"name": "age", "type": "int"}
  ]
}
';
$valueSchema = AvroSchema::parse($valueSchemaJson);

$resolver = new CallableResolver(
    constt(
        AvroSchema::parse($valueSchemaJson)
    )
);

$record = [ 'foo' => 'bar' ];

$schema = $resolver->valueSchemaFor($record);

Assert::assertEquals($schema, $valueSchema);

DefinitionInterfaceResolver

This library also provides a HasSchemaDefinitionInterface that exposes two static methods:

  • HasSchemaDefinitionInterface::valueSchemaJson returns the schema definition for the value as JSON string
  • HasSchemaDefinitionInterface::keySchemaJson returns either NULL or the schema definition for the key as JSON string.

The DefinitionInterfaceResolver checks if a given record implements that interface (if not it will throw an InvalidArgumentException) and resolves the schemas via the static methods.

<?php

namespace MyNamespace;

use FlixTech\AvroSerializer\Objects\HasSchemaDefinitionInterface;
use FlixTech\AvroSerializer\Objects\SchemaResolvers\DefinitionInterfaceResolver;

class MyRecord implements HasSchemaDefinitionInterface {
    public static function valueSchemaJson() : string
    {
        return '
               {
                 "type": "record",
                 "name": "user",
                 "fields": [
                   {"name": "name", "type": "string"},
                   {"name": "age", "type": "int"}
                 ]
               }
               ';
    }
    
    public static function keySchemaJson() : ?string
    {
        return '{"type": "string"}';
    }
}

$record = new MyRecord();

$resolver = new DefinitionInterfaceResolver();

$resolver->valueSchemaFor($record); // Will resolve from $record::valueSchemaJson();
$resolver->keySchemaFor($record); // Will resolve from $record::keySchemaJson();

ChainResolver

The chain resolver is a useful tool for composing multiple resolvers. The first resolver to be able to resolve a schema will win. If none of the resolvers in the chain is able to determine a schema, an InvalidArgumentException is thrown.

<?php

namespace MyNamespace;

use FlixTech\AvroSerializer\Objects\SchemaResolvers\ChainResolver;

$record = ['foo' => 'bar'];

/** @var \FlixTech\AvroSerializer\Objects\SchemaResolvers\FileResolver $fileResolver */
/** @var \FlixTech\AvroSerializer\Objects\SchemaResolvers\CallableResolver $callableResolver */

$resolver = new ChainResolver($fileResolver, $callableResolver);
// or new ChainResolver(...[$fileResolver, $callableResolver]);

$resolver->valueSchemaFor($record); // Will resolve $fileResolver, then $callableResolver
$resolver->keySchemaFor($record); // Will resolve $fileResolver, then $callableResolver

Symfony Serializer Integration

This library provides integrations with the Symfony Serializer component.

<?php

use FlixTech\AvroSerializer\Integrations\Symfony\Serializer\AvroSerDeEncoder;
use FlixTech\AvroSerializer\Objects\DefaultRecordSerializerFactory;
use PHPUnit\Framework\Assert;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;

class User
{
    /** @var string */
    private $name;

    /** @var int */
    private $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getAge(): int
    {
        return $this->age;
    }

    public function setAge(int $age): void
    {
        $this->age = $age;
    }
}

$recordSerializer = DefaultRecordSerializerFactory::get(
    getenv('SCHEMA_REGISTRY_HOST')
);

$avroSchemaJson = '{
  "type": "record",
  "name": "user",
  "fields": [
    {"name": "name", "type": "string"},
    {"name": "age", "type": "int"}
  ]
}';

$user = new User('Thomas', 38);

$normalizer = new GetSetMethodNormalizer();
$encoder = new AvroSerDeEncoder($recordSerializer);

$symfonySerializer = new Serializer([$normalizer], [$encoder]);

$serialized = $symfonySerializer->serialize(
    $user,
    AvroSerDeEncoder::FORMAT_AVRO,
    [
        AvroSerDeEncoder::CONTEXT_ENCODE_SUBJECT => 'users-value',
        AvroSerDeEncoder::CONTEXT_ENCODE_WRITERS_SCHEMA => AvroSchema::parse($avroSchemaJson),
    ]
);

$deserializedUser = $symfonySerializer->deserialize(
    $serialized,
    User::class,
    AvroSerDeEncoder::FORMAT_AVRO
);

Assert::assertEquals($deserializedUser, $user);

Name converter

Sometimes your property names may differ from the names of the fields in your schema. One option to solve this is by using custom Serializer annotations. However, if you're using the annotations provided by this library, you may use our name converter that parses these annotations and maps between the schema field names and the property names.

<?php

use FlixTech\AvroSerializer\Integrations\Symfony\Serializer\AvroSerDeEncoder;
use FlixTech\AvroSerializer\Integrations\Symfony\Serializer\NameConverter\AvroNameConverter;
use FlixTech\AvroSerializer\Objects\DefaultRecordSerializerFactory;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use FlixTech\AvroSerializer\Objects\Schema\Generation\AnnotationReader;

$recordSerializer = DefaultRecordSerializerFactory::get(
    getenv('SCHEMA_REGISTRY_HOST')
);

AnnotationRegistry::registerLoader('class_exists');

$reader = new AnnotationReader(
    new DoctrineAnnotationReader()
);

$nameConverter = new AvroNameConverter($reader);

$normalizer = new GetSetMethodNormalizer(null, $nameConverter);
$encoder = new AvroSerDeEncoder($recordSerializer);

$symfonySerializer = new Serializer([$normalizer], [$encoder]);

Schema builder

This library also provides means of defining schemas using php, very similar to the SchemaBuilder API provided by the Java SDK:

<?php

use FlixTech\AvroSerializer\Objects\Schema;
use FlixTech\AvroSerializer\Objects\Schema\Record\FieldOption;

Schema::record()
    ->name('object')
    ->namespace('org.acme')
    ->doc('A test object')
    ->aliases(['stdClass', 'array'])
    ->field('name', Schema::string(), FieldOption::doc('Name of the object'), FieldOption::orderDesc())
    ->field('answer', Schema::int(), FieldOption::default(42), FieldOption::orderAsc(), FieldOption::aliases('wrong', 'correct'))
    ->field('ignore', Schema::boolean(), FieldOption::orderIgnore())
    ->parse();

Schema generator

Besides providing a fluent api for defining schemas, we also provide means of generating schema from class metadata (annotations). For this to work, you have to install the doctrine/annotations package.

<?php

use FlixTech\AvroSerializer\Objects\DefaultSchemaGeneratorFactory;
use FlixTech\AvroSerializer\Objects\Schema\Generation\Annotations as SerDe;

/**
 * @SerDe\AvroType("record")
 * @SerDe\AvroName("user")
 */
class User
{
    /**
     * @SerDe\AvroType("string")
     * @var string
     */
    private $firstName;

    /**
     * @SerDe\AvroType("string")
     * @var string
     */
    private $lastName;

    /**
     * @SerDe\AvroType("int")
     * @var int
     */
    private $age;

    public function __construct(string $firstName, string $lastName, int $age)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->age = $age;
    }

    public function getFirstName(): string
    {
        return $this->firstName;
    }

    public function getLastName(): string
    {
        return $this->lastName;
    }

    public function getAge(): int
    {
        return $this->age;
    }
}

$generator = DefaultSchemaGeneratorFactory::get();

$schema = $generator->generate(User::class);
$avroSchema = $schema->parse();

Further examples on the possible annotations can be seen in the test case.

Examples

This library provides a few executable examples in the examples folder. You should have a look to get an understanding how this library works.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].