Skip to content

Commit 9f2f188

Browse files
authored
Add support for schema generation (#37)
* Create the SchemaGenerator class and define the SchemaAttributeReader and SchemaAttribute interfaces. This allows us to support php8 attributes in the future. * Create the AnnotationReader adapter that parses class annotations. * Add documentation and further examples. * Increment the miminum phpstan version. * Refactor Schema builder to use constants instead of literal strings. Thanks to @fcoedno for the contribution!
1 parent 14e6840 commit 9f2f188

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1565
-69
lines changed

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,72 @@ Schema::record()
380380
->parse();
381381
```
382382

383+
## Schema generator
384+
385+
Besides providing a fluent api for defining schemas, we also provide means of generating schema from
386+
class metadata (annotations). For this to work, you have to install the `doctrine/annotations` package.
387+
388+
```php
389+
<?php
390+
391+
use FlixTech\AvroSerializer\Objects\DefaultSchemaGeneratorFactory;
392+
use FlixTech\AvroSerializer\Objects\Schema\Generation\Annotations as SerDe;
393+
394+
/**
395+
* @SerDe\AvroType("record")
396+
* @SerDe\AvroName("user")
397+
*/
398+
class User
399+
{
400+
/**
401+
* @SerDe\AvroType("string")
402+
* @var string
403+
*/
404+
private $firstName;
405+
406+
/**
407+
* @SerDe\AvroType("string")
408+
* @var string
409+
*/
410+
private $lastName;
411+
412+
/**
413+
* @SerDe\AvroType("int")
414+
* @var int
415+
*/
416+
private $age;
417+
418+
public function __construct(string $firstName, string $lastName, int $age)
419+
{
420+
$this->firstName = $firstName;
421+
$this->lastName = $lastName;
422+
$this->age = $age;
423+
}
424+
425+
public function getFirstName(): string
426+
{
427+
return $this->firstName;
428+
}
429+
430+
public function getLastName(): string
431+
{
432+
return $this->lastName;
433+
}
434+
435+
public function getAge(): int
436+
{
437+
return $this->age;
438+
}
439+
}
440+
441+
$generator = DefaultSchemaGeneratorFactory::get();
442+
443+
$schema = $generator->generate(User::class);
444+
$avroSchema = $schema->parse();
445+
```
446+
447+
Further examples on the possible annotations can be seen in the [test case](test/Objects/Schema/Generation/SchemaGeneratorTest.php).
448+
383449
## Examples
384450

385451
This library provides a few executable examples in the [examples](examples) folder. You should have a look to get an

composer.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
},
3232
"require-dev": {
3333
"phpunit/phpunit": "~7.0,<8.0",
34-
"phpstan/phpstan": "^0.12",
34+
"phpstan/phpstan": "^0.12.24",
3535
"phpbench/phpbench": "~0.9",
3636
"vlucas/phpdotenv": "~2.4",
37-
"symfony/serializer": "^3.4|^4.3"
37+
"symfony/serializer": "^3.4|^4.3",
38+
"doctrine/annotations": "^1.10"
3839
},
3940
"autoload": {
4041
"psr-4": {
@@ -48,7 +49,8 @@
4849
]
4950
},
5051
"suggest": {
51-
"symfony/serializer": "To integrate avro-serde-php into symfony ecosystem"
52+
"symfony/serializer": "To integrate avro-serde-php into symfony ecosystem",
53+
"doctrine/annotations": "To enable the generation of avro schemas from annotations"
5254
},
5355
"autoload-dev": {
5456
"psr-4": {

examples/SchemaGenerator.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\AvroSerializer\Examples;
6+
7+
use Dotenv\Dotenv;
8+
use FlixTech\AvroSerializer\Integrations\Symfony\Serializer\AvroSerDeEncoder;
9+
use FlixTech\AvroSerializer\Objects\DefaultRecordSerializerFactory;
10+
use FlixTech\AvroSerializer\Objects\DefaultSchemaGeneratorFactory;
11+
use PHPUnit\Framework\Assert;
12+
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
13+
use Symfony\Component\Serializer\Serializer;
14+
use FlixTech\AvroSerializer\Objects\Schema\Generation\Annotations as SerDe;
15+
16+
require __DIR__ . '/../vendor/autoload.php';
17+
18+
$dotEnv = new Dotenv(__DIR__ . '/..');
19+
$dotEnv->load();
20+
$dotEnv->required('SCHEMA_REGISTRY_HOST')->notEmpty();
21+
22+
/**
23+
* @SerDe\AvroType("record")
24+
* @SerDe\AvroName("user")
25+
*/
26+
class WriterUser
27+
{
28+
/**
29+
* @SerDe\AvroType("string")
30+
* @var string
31+
*/
32+
private $firstName;
33+
34+
/**
35+
* @SerDe\AvroType("string")
36+
* @var string
37+
*/
38+
private $lastName;
39+
40+
/**
41+
* @SerDe\AvroType("int")
42+
* @var int
43+
*/
44+
private $age;
45+
46+
public function __construct(string $firstName, string $lastName, int $age)
47+
{
48+
$this->firstName = $firstName;
49+
$this->lastName = $lastName;
50+
$this->age = $age;
51+
}
52+
53+
public function getFirstName(): string
54+
{
55+
return $this->firstName;
56+
}
57+
58+
public function getLastName(): string
59+
{
60+
return $this->lastName;
61+
}
62+
63+
public function getAge(): int
64+
{
65+
return $this->age;
66+
}
67+
}
68+
69+
/**
70+
* @SerDe\AvroType("record")
71+
* @SerDe\AvroName("user")
72+
*/
73+
class ReaderUser
74+
{
75+
/**
76+
* @SerDe\AvroType("string")
77+
* @var string
78+
*/
79+
private $firstName;
80+
81+
/**
82+
* @SerDe\AvroType("int")
83+
* @var int
84+
*/
85+
private $age;
86+
87+
public function __construct(string $firstName, int $age)
88+
{
89+
$this->firstName = $firstName;
90+
$this->age = $age;
91+
}
92+
93+
public function getFirstName(): string
94+
{
95+
return $this->firstName;
96+
}
97+
98+
public function getAge(): int
99+
{
100+
return $this->age;
101+
}
102+
}
103+
104+
$recordSerializer = DefaultRecordSerializerFactory::get(getenv('SCHEMA_REGISTRY_HOST'));
105+
$schemaGenerator = DefaultSchemaGeneratorFactory::get();
106+
107+
$user = new WriterUser('Francisco', 'Rodrigues', 42);
108+
109+
echo "User object to be serialized:\n";
110+
echo \var_export($user, true) . "\n\n";
111+
112+
$normalizer = new PropertyNormalizer();
113+
$encoder = new AvroSerDeEncoder($recordSerializer);
114+
115+
$symfonySerializer = new Serializer([$normalizer], [$encoder]);
116+
117+
$serialized = $symfonySerializer->serialize(
118+
$user,
119+
AvroSerDeEncoder::FORMAT_AVRO,
120+
[
121+
AvroSerDeEncoder::CONTEXT_ENCODE_SUBJECT => 'users-value',
122+
AvroSerDeEncoder::CONTEXT_ENCODE_WRITERS_SCHEMA => $schemaGenerator->generate(WriterUser::class)->parse(),
123+
]
124+
);
125+
126+
echo "Confluent Avro wire format serialized binary as hex:\n";
127+
echo bin2hex($serialized) . "\n\n";
128+
129+
/** @var ReaderUser $deserializedUser */
130+
$deserializedUser = $symfonySerializer->deserialize($serialized, ReaderUser::class, AvroSerDeEncoder::FORMAT_AVRO, [
131+
AvroSerDeEncoder::CONTEXT_DECODE_READERS_SCHEMA => $schemaGenerator->generate(ReaderUser::class)->parse(),
132+
]);
133+
134+
echo "Deserialized User:\n";
135+
echo \var_export($deserializedUser, true) . "\n";
136+
137+
Assert::assertEquals(
138+
$user->getFirstName(),
139+
$deserializedUser->getFirstName()
140+
);
141+
142+
Assert::assertEquals(
143+
$user->getAge(),
144+
$deserializedUser->getAge()
145+
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\AvroSerializer\Objects;
6+
7+
use Doctrine\Common\Annotations\AnnotationReader;
8+
use Doctrine\Common\Annotations\AnnotationRegistry;
9+
use FlixTech\AvroSerializer\Objects\Schema\Generation\SchemaGenerator;
10+
11+
class DefaultSchemaGeneratorFactory
12+
{
13+
public static function get(): SchemaGenerator
14+
{
15+
AnnotationRegistry::registerLoader('class_exists');
16+
17+
return new Schema\Generation\SchemaGenerator(
18+
new Schema\Generation\AnnotationReader(
19+
new AnnotationReader()
20+
)
21+
);
22+
}
23+
}

src/Objects/Schema/ArrayType.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ class ArrayType extends ComplexType
1010
{
1111
public function __construct()
1212
{
13-
parent::__construct('array');
13+
parent::__construct(TypeName::ARRAY);
1414
}
1515

1616
public function items(Schema $schema): self
1717
{
18-
return $this->attribute('items', $schema);
18+
return $this->attribute(AttributeName::ITEMS, $schema);
1919
}
2020

2121
/**
2222
* @param array<mixed> $default
2323
*/
2424
public function default(array $default): self
2525
{
26-
return $this->attribute('default', $default);
26+
return $this->attribute(AttributeName::DEFAULT, $default);
2727
}
2828
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\AvroSerializer\Objects\Schema;
6+
7+
class AttributeName
8+
{
9+
public const ALIASES = 'aliases';
10+
public const DEFAULT = 'default';
11+
public const DOC = 'doc';
12+
public const ITEMS = 'items';
13+
public const NAME = 'name';
14+
public const NAMESPACE = 'namespace';
15+
public const ORDER = 'order';
16+
public const SIZE = 'size';
17+
public const SYMBOLS = 'symbols';
18+
public const TARGET_CLASS = 'targetClass';
19+
public const TYPE = 'type';
20+
public const VALUES = 'values';
21+
public const LOGICAL_TYPE = 'logicalType';
22+
public const FIELDS = 'fields';
23+
}

src/Objects/Schema/BooleanType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ class BooleanType extends PrimitiveType
88
{
99
public function __construct()
1010
{
11-
parent::__construct('boolean');
11+
parent::__construct(TypeName::BOOLEAN);
1212
}
1313
}

src/Objects/Schema/BytesType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ class BytesType extends PrimitiveType
88
{
99
public function __construct()
1010
{
11-
parent::__construct('bytes');
11+
parent::__construct(TypeName::BYTES);
1212
}
1313
}

src/Objects/Schema/DateType.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44

55
namespace FlixTech\AvroSerializer\Objects\Schema;
66

7-
use FlixTech\AvroSerializer\Objects\Schema;
8-
97
class DateType extends LogicalType
108
{
119
public function __construct()
1210
{
1311
parent::__construct(
14-
'date',
15-
Schema::int()->serialize()
12+
TypeName::DATE,
13+
TypeName::INT
1614
);
1715
}
1816
}

src/Objects/Schema/DoubleType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ class DoubleType extends PrimitiveType
88
{
99
public function __construct()
1010
{
11-
parent::__construct('double');
11+
parent::__construct(TypeName::DOUBLE);
1212
}
1313
}

0 commit comments

Comments
 (0)