Skip to content

Commit c9493dc

Browse files
committed
chore: add missing test cases; rewrite README
1 parent f0d608d commit c9493dc

6 files changed

Lines changed: 138 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ For this reason, we have written this plugin, which — in addition to addressin
2020
- [repeated](#repeated)
2121
- [maps](#maps)
2222
- [oneof](#oneof)
23-
- [precedence](#precedence)
23+
- [presence](#presence)
2424
- [grpc client](#grpc-client)
2525
- [grpc server](#grpc-server)
2626
- [autoload.metadata.php](#autoloadmetadataphp)
@@ -366,10 +366,26 @@ final readonly class ContactPhone implements \Thesis\Api\Request\Contact
366366

367367
#### presence
368368

369-
By default, all fields with scalar data types will have corresponding default values (0 for numbers, false for booleans, and so on).
370-
If proto2 is used and the field is marked as `optional`, scalar types will become nullable, with null as the default value.
371-
The same applies to `optional` in proto3. Lists and maps, however, will always be non-nullable (especially since they cannot be `required` or `optional`) but will have empty default values.
372-
Meanwhile, all objects will always be nullable, regardless of `required`/`optional` labels, which allows the serializer to quickly skip such fields and avoid writing unnecessary data.
369+
The plugin resolves effective field presence from syntax/edition features and generates constructor signatures accordingly.
370+
For editions, inherited features are resolved through `file` -> `message` -> `field`.
371+
372+
The generator decides nullable/default/required for each field using these rules:
373+
374+
1. A field is generated as required constructor argument (no default) when either:
375+
- proto2 label is `required`;
376+
- editions effective `features.field_presence = LEGACY_REQUIRED`.
377+
2. `repeated` fields are always non-nullable and always default to `[]`.
378+
3. `map` fields are always non-nullable and default to `new \Thesis\Protobuf\Map()`.
379+
4. `oneof` container property is always nullable (`?T = null`), because "no active variant" is valid.
380+
5. Scalar fields with explicit presence (for example `optional` in proto2/proto3, or editions `EXPLICIT`) are nullable and default to `null`.
381+
6. Scalar fields with implicit presence are non-nullable and use protobuf zero defaults.
382+
7. Enum fields:
383+
- without explicit field default: use enum zero/first case default for non-nullable fields;
384+
- with explicit proto default (`[default = ...]`): generated as non-nullable with that enum case as PHP default;
385+
- nullable enum fields default to `null`.
386+
8. Message/object fields are nullable by default and default to `null`; if such field is required (rule 1), it becomes a required constructor argument.
387+
388+
For nullable fields, proto-defined defaults are intentionally not baked into constructor defaults, so "unset" can be distinguished from "set to proto default".
373389

374390
#### grpc client
375391

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
syntax = "proto2";
2+
3+
package proto.required.v1;
4+
5+
message Inner {
6+
optional string name = 1;
7+
}
8+
9+
message Request {
10+
required Inner required_inner = 1;
11+
optional Inner optional_inner = 2;
12+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/**
4+
* Code generated by thesis/protoc-plugin. DO NOT EDIT.
5+
* Versions:
6+
* thesis/protoc-plugin — v0.1.x-dev
7+
* protoc — v6.32.1
8+
* Source: required_message.proto
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace Proto\Required\V1;
14+
15+
use Thesis\Protobuf\Reflection;
16+
17+
/**
18+
* @api
19+
*/
20+
final readonly class Inner
21+
{
22+
public function __construct(
23+
#[Reflection\Field(1, Reflection\StringT::T)]
24+
public ?string $name = null,
25+
) {}
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/**
4+
* Code generated by thesis/protoc-plugin. DO NOT EDIT.
5+
* Versions:
6+
* thesis/protoc-plugin — v0.1.x-dev
7+
* protoc — v6.32.1
8+
* Source: required_message.proto
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace Proto\Required\V1;
14+
15+
use Thesis\Protobuf\Reflection;
16+
17+
/**
18+
* @api
19+
*/
20+
final readonly class Request
21+
{
22+
public function __construct(
23+
#[Reflection\Field(1, new Reflection\ObjectT(\Proto\Required\V1\Inner::class))]
24+
public \Proto\Required\V1\Inner $requiredInner,
25+
#[Reflection\Field(2, new Reflection\ObjectT(\Proto\Required\V1\Inner::class))]
26+
public ?\Proto\Required\V1\Inner $optionalInner = null,
27+
) {}
28+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/**
4+
* Code generated by thesis/protoc-plugin. DO NOT EDIT.
5+
* Versions:
6+
* thesis/protoc-plugin — v0.1.x-dev
7+
* protoc — v6.32.1
8+
* Source: required_message.proto
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace Proto\Required\V1;
14+
15+
use Override;
16+
use Thesis\Protobuf\Registry;
17+
use Thesis\Protobuf\Registry\File;
18+
19+
/**
20+
* @api
21+
*/
22+
final readonly class RequiredMessageDescriptorRegistry implements Registry\Registrar
23+
{
24+
private const string DESCRIPTOR_BUFFER = 'ChZyZXF1aXJlZF9tZXNzYWdlLnByb3RvEhFwcm90by5yZXF1aXJlZC52MSIbCgVJbm5lchISCgRuYW1lGAEgASgJUgRuYW1lIosBCgdSZXF1ZXN0Ej8KDnJlcXVpcmVkX2lubmVyGAEgAigLMhgucHJvdG8ucmVxdWlyZWQudjEuSW5uZXJSDXJlcXVpcmVkSW5uZXISPwoOb3B0aW9uYWxfaW5uZXIYAiABKAsyGC5wcm90by5yZXF1aXJlZC52MS5Jbm5lclINb3B0aW9uYWxJbm5lckqbAgoGEgQAAAsBCggKAQwSAwAAEgoICgECEgMCABoKCgoCBAASBAQABgEKCgoDBAABEgMECA0KCwoEBAACABIDBQQdCgwKBQQAAgAEEgMFBAwKDAoFBAACAAUSAwUNEwoMCgUEAAIAARIDBRQYCgwKBQQAAgADEgMFGxwKCgoCBAESBAgACwEKCgoDBAEBEgMICA8KCwoEBAECABIDCQQmCgwKBQQBAgAEEgMJBAwKDAoFBAECAAYSAwkNEgoMCgUEAQIAARIDCRMhCgwKBQQBAgADEgMJJCUKCwoEBAECARIDCgQmCgwKBQQBAgEEEgMKBAwKDAoFBAECAQYSAwoNEgoMCgUEAQIBARIDChMhCgwKBQQBAgEDEgMKJCU=';
25+
26+
#[Override]
27+
public function register(Registry\Pool $pool): void
28+
{
29+
$pool->add(Registry\Descriptor::base64(self::DESCRIPTOR_BUFFER), new File(
30+
name: 'required_message.proto',
31+
messages: [
32+
new File\MessageDescriptor('proto.required.v1.Inner', \Proto\Required\V1\Inner::class),
33+
new File\MessageDescriptor('proto.required.v1.Request', \Proto\Required\V1\Request::class),
34+
],
35+
));
36+
}
37+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/**
4+
* Code generated by thesis/protoc-plugin. DO NOT EDIT.
5+
* Versions:
6+
* thesis/protoc-plugin — v0.1.x-dev
7+
* protoc — v6.32.1
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
\Thesis\Protobuf\Registry\Pool::get()->register(
13+
new \Thesis\Protobuf\Registry\OnceRegistrar(new \Proto\Required\V1\RequiredMessageDescriptorRegistry()),
14+
);

0 commit comments

Comments
 (0)