{"id":21042580,"url":"https://github.com/flix-tech/avro-serde-php","last_synced_at":"2025-04-08T08:17:22.580Z","repository":{"id":41322356,"uuid":"146994007","full_name":"flix-tech/avro-serde-php","owner":"flix-tech","description":"Avro Serialisation/Deserialisation (SerDe) library for PHP 8.1+ with a Symfony Serializer integration","archived":false,"fork":false,"pushed_at":"2024-12-25T04:59:35.000Z","size":210,"stargazers_count":65,"open_issues_count":17,"forks_count":37,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-01T07:42:06.277Z","etag":null,"topics":["avro","avro-format","avro-schema","confluent","confluent-platform","deserialization","php","serde","serialization","symfony","symfony-serializer"],"latest_commit_sha":null,"homepage":"https://www.flix.tech/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flix-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-01T11:48:58.000Z","updated_at":"2025-01-15T22:33:01.000Z","dependencies_parsed_at":"2024-06-18T12:31:49.725Z","dependency_job_id":"f64f1f6d-2dbd-4294-b5bf-bbde869f47a8","html_url":"https://github.com/flix-tech/avro-serde-php","commit_stats":{"total_commits":54,"total_committers":10,"mean_commits":5.4,"dds":"0.37037037037037035","last_synced_commit":"ffeab369fe3631d2a076ef9744f0ee2dcab2238c"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flix-tech%2Favro-serde-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flix-tech%2Favro-serde-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flix-tech%2Favro-serde-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flix-tech%2Favro-serde-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flix-tech","download_url":"https://codeload.github.com/flix-tech/avro-serde-php/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247801175,"owners_count":20998339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avro","avro-format","avro-schema","confluent","confluent-platform","deserialization","php","serde","serialization","symfony","symfony-serializer"],"created_at":"2024-11-19T14:07:58.997Z","updated_at":"2025-04-08T08:17:22.562Z","avatar_url":"https://github.com/flix-tech.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Avro SerDe for PHP 8.1+\n\n[![php-confluent-serde Actions Status](https://github.com/flix-tech/avro-serde-php/workflows/php-confluent-serde/badge.svg?branch=master)](https://github.com/flix-tech/avro-serde-php/actions)\n[![Maintainability](https://api.codeclimate.com/v1/badges/7500470a6812cf5a1ad5/maintainability)](https://codeclimate.com/github/flix-tech/avro-serde-php/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/7500470a6812cf5a1ad5/test_coverage)](https://codeclimate.com/github/flix-tech/avro-serde-php/test_coverage)\n[![Latest Stable Version](https://poser.pugx.org/flix-tech/avro-serde-php/version)](https://packagist.org/packages/flix-tech/avro-serde-php)\n[![Total Downloads](https://poser.pugx.org/flix-tech/avro-serde-php/downloads)](https://packagist.org/packages/flix-tech/avro-serde-php)\n[![License](https://poser.pugx.org/flix-tech/avro-serde-php/license)](https://packagist.org/packages/flix-tech/avro-serde-php)\n\n## Motivation\n\nWhen serializing and deserializing messages using the [Avro](http://avro.apache.org/docs/current/) serialization format,\nespecially when integrating with the [Confluent Platform](https://docs.confluent.io/current/avro.html), you want to make\nsure that schemas are evolved in a way that downstream consumers are not affected.\n\nHence, [Confluent](https://www.confluent.io/) developed the\n[Schema Registry](https://docs.confluent.io/current/schema-registry/docs/index.html) which has the responsibility to\nvalidate a given schema evolution against a configurable compatibility policy.\n\nUnfortunately Confluent is not providing an official Avro SerDe package for PHP. This library aims to provide an Avro\nSerDe library for PHP that implements the\n[Confluent wire format](https://docs.confluent.io/current/schema-registry/docs/serializer-formatter.html#wire-format) and\nintegrates FlixTech's [Schema Registry Client](https://github.com/flix-tech/schema-registry-php-client).\n\n## Installation\n\nThis library is using the [composer package manager](https://getcomposer.org/) for PHP.\n\n```bash\ncomposer require 'flix-tech/avro-serde-php:^2.2'\n```\n\n## Quickstart\n\n\u003e **NOTE**\n\u003e\n\u003e You should **always** use a cached schema registry client, since otherwise you'd make an HTTP request for every\n\u003e message serialized or deserialized.\n\n### 1. Create a cached Schema Registry client\n\nSee the [Schema Registry client documentation on caching](https://github.com/flix-tech/schema-registry-php-client#caching)\nfor more detailed information.\n\n```php\n\u003c?php\n\nuse FlixTech\\SchemaRegistryApi\\Registry\\Cache\\AvroObjectCacheAdapter;\nuse FlixTech\\SchemaRegistryApi\\Registry\\CachedRegistry;\nuse FlixTech\\SchemaRegistryApi\\Registry\\PromisingRegistry;\nuse GuzzleHttp\\Client;\n\n$schemaRegistryClient = new CachedRegistry(\n    new PromisingRegistry(\n        new Client(['base_uri' =\u003e 'registry.example.com'])\n    ),\n    new AvroObjectCacheAdapter()\n);\n```\n\n### 2. Build the `RecordSerializer` instance\n\nThe `RecordSerializer` is the main way you interact with this library. It provides the `encodeRecord` and\n`decodeMessage` methods for SerDe operations.\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Objects\\RecordSerializer;\n\n/** @var \\FlixTech\\SchemaRegistryApi\\Registry $schemaRegistry */\n$recordSerializer = new RecordSerializer(\n    $schemaRegistry,\n    [\n        // If you want to auto-register missing schemas set this to true\n        RecordSerializer::OPTION_REGISTER_MISSING_SCHEMAS =\u003e false,\n        // If you want to auto-register missing subjects set this to true\n        RecordSerializer::OPTION_REGISTER_MISSING_SUBJECTS =\u003e false,\n    ]\n);\n```\n\n### 3. Encoding records\n\nThis is a simple example on how you can use the `RecordSerializer` to encode messages in the Confluent Avro wire format.\n\n```php\n\u003c?php\n\n/** @var \\FlixTech\\AvroSerializer\\Objects\\RecordSerializer $recordSerializer */\n$subject = 'my-topic-value';\n$avroSchema = AvroSchema::parse('{\"type\": \"string\"}');\n$record = 'Test message';\n\n$encodedBinaryAvro = $recordSerializer-\u003eencodeRecord($subject, $avroSchema, $record);\n// Send this over the wire...\n```\n\n### 4. Decoding messages\n\nThis is a simple example on how you can use the `RecordSerializer` to decode messages.\n\n```php\n\u003c?php\n\n/** @var \\FlixTech\\AvroSerializer\\Objects\\RecordSerializer $recordSerializer */\n/** @var string $encodedBinaryAvro */\n$record = $recordSerializer-\u003edecodeMessage($encodedBinaryAvro);\n\necho $record; // 'Test message'\n```\n\n## Schema Resolvers\n\nSchema Resolvers are responsible to know which Avro schema belongs to which type of record. This is especially useful\nif you want to manage your Avro schemas in separate files. Schema Resolvers enable you to integrate with whatever schema\nmanagement concept you may have outside of the scope of this library.\n\nSchema Resolvers take a `$record` of any type and try to resolve a matching [`AvroSchema`]() instance for it.\n\n### FileResolver\n\nIn even moderately complicated applications you want to manage your schemas within the VCS, most probably as `.avsc`\nfiles. These files contain JSON that is describing the Avro schema.\n\nThe resolver takes a `$baseDir` in which you want to manage the files and an inflector `callable`, which is a simple\nfunction that takes the record as first parameter, and a second boolean `$isKey` parameter indicating if the inflection\nis targeting a key schema.\n\n```php\n\u003c?php\n\nnamespace MyNamespace;\n\nuse FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\FileResolver;\nuse function get_class;use function is_object;\nuse function str_replace;\n\nclass MyRecord {}\n\n$record = new MyRecord();\n\n$baseDir = __DIR__ . '/files';\n\n$inflector = static function ($record, bool $isKey) {\n    $ext = $isKey ? '.key.avsc' : '.avsc';\n    $fileName = is_object($record)\n        ? str_replace('\\\\', '.', get_class($record))\n        : 'default';\n    \n    return $fileName . $ext;\n};\n\n\necho $inflector($record, false); // MyNamespace.MyRecord.avsc\necho $inflector($record, true); // MyNamespace.MyRecord.key.avsc\n\n$resolver = new FileResolver($baseDir, $inflector);\n\n$resolver-\u003evalueSchemaFor($record); // This will load from $baseDir . '/' . MyNamespace.MyRecord.avsc\n$resolver-\u003ekeySchemaFor($record); // This will load from $baseDir . '/' . MyNamespace.MyRecord.key.avsc\n```\n\n### CallableResolver\n\nThis is the simplest but also most flexible resolver. It just takes two `callables` that are responsible to fetch either\nvalue- or key-schemas respectively. A key schema resolver is optional.\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\CallableResolver;\nuse PHPUnit\\Framework\\Assert;\nuse function Widmogrod\\Functional\\constt;\n\n$valueSchemaJson = '\n{\n  \"type\": \"record\",\n  \"name\": \"user\",\n  \"fields\": [\n    {\"name\": \"name\", \"type\": \"string\"},\n    {\"name\": \"age\", \"type\": \"int\"}\n  ]\n}\n';\n$valueSchema = AvroSchema::parse($valueSchemaJson);\n\n$resolver = new CallableResolver(\n    constt(\n        AvroSchema::parse($valueSchemaJson)\n    )\n);\n\n$record = [ 'foo' =\u003e 'bar' ];\n\n$schema = $resolver-\u003evalueSchemaFor($record);\n\nAssert::assertEquals($schema, $valueSchema);\n```\n\n### DefinitionInterfaceResolver\n\nThis library also provides a [`HasSchemaDefinitionInterface`](src/Objects/HasSchemaDefinitionInterface.php)\nthat exposes two static methods:\n\n* `HasSchemaDefinitionInterface::valueSchemaJson` returns the schema definition for the value as JSON string\n* `HasSchemaDefinitionInterface::keySchemaJson` returns either `NULL` or the schema definition for the key as JSON\nstring.\n\nThe `DefinitionInterfaceResolver` checks if a given record implements that interface (if not it will throw an \n`InvalidArgumentException`) and resolves the schemas via the static methods.\n\n```php\n\u003c?php\n\nnamespace MyNamespace;\n\nuse FlixTech\\AvroSerializer\\Objects\\HasSchemaDefinitionInterface;\nuse FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\DefinitionInterfaceResolver;\n\nclass MyRecord implements HasSchemaDefinitionInterface {\n    public static function valueSchemaJson() : string\n    {\n        return '\n               {\n                 \"type\": \"record\",\n                 \"name\": \"user\",\n                 \"fields\": [\n                   {\"name\": \"name\", \"type\": \"string\"},\n                   {\"name\": \"age\", \"type\": \"int\"}\n                 ]\n               }\n               ';\n    }\n    \n    public static function keySchemaJson() : ?string\n    {\n        return '{\"type\": \"string\"}';\n    }\n}\n\n$record = new MyRecord();\n\n$resolver = new DefinitionInterfaceResolver();\n\n$resolver-\u003evalueSchemaFor($record); // Will resolve from $record::valueSchemaJson();\n$resolver-\u003ekeySchemaFor($record); // Will resolve from $record::keySchemaJson();\n```\n\n### ChainResolver\n\nThe chain resolver is a useful tool for composing multiple resolvers. The first resolver to be able to resolve a schema\nwill win. If none of the resolvers in the chain is able to determine a schema, an `InvalidArgumentException` is thrown.\n\n```php\n\u003c?php\n\nnamespace MyNamespace;\n\nuse FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\ChainResolver;\n\n$record = ['foo' =\u003e 'bar'];\n\n/** @var \\FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\FileResolver $fileResolver */\n/** @var \\FlixTech\\AvroSerializer\\Objects\\SchemaResolvers\\CallableResolver $callableResolver */\n\n$resolver = new ChainResolver($fileResolver, $callableResolver);\n// or new ChainResolver(...[$fileResolver, $callableResolver]);\n\n$resolver-\u003evalueSchemaFor($record); // Will resolve $fileResolver, then $callableResolver\n$resolver-\u003ekeySchemaFor($record); // Will resolve $fileResolver, then $callableResolver\n```\n\n## Symfony Serializer Integration\n\nThis library provides integrations with the [Symfony Serializer component](https://symfony.com/doc/master/components/serializer.html) from version 6.4 and above.\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Integrations\\Symfony\\Serializer\\AvroSerDeEncoder;\nuse FlixTech\\AvroSerializer\\Objects\\DefaultRecordSerializerFactory;\nuse PHPUnit\\Framework\\Assert;\nuse Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer;\nuse Symfony\\Component\\Serializer\\Serializer;\n\nclass User\n{\n    /** @var string */\n    private $name;\n\n    /** @var int */\n    private $age;\n\n    public function __construct(string $name, int $age)\n    {\n        $this-\u003ename = $name;\n        $this-\u003eage = $age;\n    }\n\n    public function getName(): string\n    {\n        return $this-\u003ename;\n    }\n\n    public function setName(string $name): void\n    {\n        $this-\u003ename = $name;\n    }\n\n    public function getAge(): int\n    {\n        return $this-\u003eage;\n    }\n\n    public function setAge(int $age): void\n    {\n        $this-\u003eage = $age;\n    }\n}\n\n$recordSerializer = DefaultRecordSerializerFactory::get(\n    getenv('SCHEMA_REGISTRY_HOST')\n);\n\n$avroSchemaJson = '{\n  \"type\": \"record\",\n  \"name\": \"user\",\n  \"fields\": [\n    {\"name\": \"name\", \"type\": \"string\"},\n    {\"name\": \"age\", \"type\": \"int\"}\n  ]\n}';\n\n$user = new User('Thomas', 38);\n\n$normalizer = new GetSetMethodNormalizer();\n$encoder = new AvroSerDeEncoder($recordSerializer);\n\n$symfonySerializer = new Serializer([$normalizer], [$encoder]);\n\n$serialized = $symfonySerializer-\u003eserialize(\n    $user,\n    AvroSerDeEncoder::FORMAT_AVRO,\n    [\n        AvroSerDeEncoder::CONTEXT_ENCODE_SUBJECT =\u003e 'users-value',\n        AvroSerDeEncoder::CONTEXT_ENCODE_WRITERS_SCHEMA =\u003e AvroSchema::parse($avroSchemaJson),\n    ]\n);\n\n$deserializedUser = $symfonySerializer-\u003edeserialize(\n    $serialized,\n    User::class,\n    AvroSerDeEncoder::FORMAT_AVRO\n);\n\nAssert::assertEquals($deserializedUser, $user);\n\n```\n\n### Name converter\n\nSometimes your property names may differ from the names of the fields in your schema. One option\nto solve this is by using [custom Serializer annotations](https://symfony.com/doc/current/components/serializer.html#configure-name-conversion-using-metadata). However, if you're using the annotations\nprovided by this library, \nyou may use our [name converter](integrations/Symfony/Serializer/NameConverter/AvroNameConverter.php)\nthat parses these annotations and maps between the schema field names and the property names.\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Integrations\\Symfony\\Serializer\\AvroSerDeEncoder;\nuse FlixTech\\AvroSerializer\\Integrations\\Symfony\\Serializer\\NameConverter\\AvroNameConverter;\nuse FlixTech\\AvroSerializer\\Objects\\DefaultRecordSerializerFactory;\nuse Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer;\nuse Symfony\\Component\\Serializer\\Serializer;\nuse Doctrine\\Common\\Annotations\\AnnotationReader as DoctrineAnnotationReader;\nuse Doctrine\\Common\\Annotations\\AnnotationRegistry;\nuse FlixTech\\AvroSerializer\\Objects\\Schema\\Generation\\AnnotationReader;\n\n$recordSerializer = DefaultRecordSerializerFactory::get(\n    getenv('SCHEMA_REGISTRY_HOST')\n);\n\nAnnotationRegistry::registerLoader('class_exists');\n\n$reader = new AnnotationReader(\n    new DoctrineAnnotationReader()\n);\n\n$nameConverter = new AvroNameConverter($reader);\n\n$normalizer = new GetSetMethodNormalizer(null, $nameConverter);\n$encoder = new AvroSerDeEncoder($recordSerializer);\n\n$symfonySerializer = new Serializer([$normalizer], [$encoder]);\n```\n\n## Schema builder\n\nThis library also provides means of defining schemas using php, very similar to \nthe [SchemaBuilder API provided by the Java SDK](https://avro.apache.org/docs/1.7.6/api/java/org/apache/avro/SchemaBuilder.html):\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Objects\\Schema;\nuse FlixTech\\AvroSerializer\\Objects\\Schema\\Record\\FieldOption;\n\nSchema::record()\n    -\u003ename('object')\n    -\u003enamespace('org.acme')\n    -\u003edoc('A test object')\n    -\u003ealiases(['stdClass', 'array'])\n    -\u003efield('name', Schema::string(), FieldOption::doc('Name of the object'), FieldOption::orderDesc())\n    -\u003efield('answer', Schema::int(), FieldOption::default(42), FieldOption::orderAsc(), FieldOption::aliases('wrong', 'correct'))\n    -\u003efield('ignore', Schema::boolean(), FieldOption::orderIgnore())\n    -\u003eparse();\n```\n\n## Schema generator\n\nBesides providing a fluent api for defining schemas, we also provide means of generating schema from \nclass metadata (annotations). For this to work, you have to install the `doctrine/annotations` package.\n\n```php\n\u003c?php\n\nuse FlixTech\\AvroSerializer\\Objects\\DefaultSchemaGeneratorFactory;\nuse FlixTech\\AvroSerializer\\Objects\\Schema\\Generation\\Annotations as SerDe;\n\n/**\n * @SerDe\\AvroType(\"record\")\n * @SerDe\\AvroName(\"user\")\n */\nclass User\n{\n    /**\n     * @SerDe\\AvroType(\"string\")\n     * @var string\n     */\n    private $firstName;\n\n    /**\n     * @SerDe\\AvroType(\"string\")\n     * @var string\n     */\n    private $lastName;\n\n    /**\n     * @SerDe\\AvroType(\"int\")\n     * @var int\n     */\n    private $age;\n\n    public function __construct(string $firstName, string $lastName, int $age)\n    {\n        $this-\u003efirstName = $firstName;\n        $this-\u003elastName = $lastName;\n        $this-\u003eage = $age;\n    }\n\n    public function getFirstName(): string\n    {\n        return $this-\u003efirstName;\n    }\n\n    public function getLastName(): string\n    {\n        return $this-\u003elastName;\n    }\n\n    public function getAge(): int\n    {\n        return $this-\u003eage;\n    }\n}\n\n$generator = DefaultSchemaGeneratorFactory::get();\n\n$schema = $generator-\u003egenerate(User::class);\n$avroSchema = $schema-\u003eparse();\n```\n\nFurther examples on the possible annotations can be seen in the [test case](test/Objects/Schema/Generation/SchemaGeneratorTest.php).\n\n## Examples\n\nThis library provides a few executable examples in the [examples](examples) folder. You should have a look to get an\nunderstanding how this library works.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflix-tech%2Favro-serde-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflix-tech%2Favro-serde-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflix-tech%2Favro-serde-php/lists"}