butschster / proto-parser
Proto解析器是一个库,可以将Protocol Buffers文件解析成抽象语法树(AST)
Requires
- php: >=8.3
- phplrt/runtime: ^3.6
Requires (Dev)
- buggregator/trap: ^1.10
- mockery/mockery: ^1.6
- phplrt/phplrt: ^3.6
- phpunit/phpunit: ^11.3
README
这是一个强大的、灵活的PHP Protocol Buffers解析器,能够从.proto
文件生成抽象语法树(AST)。此包为在PHP项目中使用Protocol Buffers提供了一个坚实的基石。
关键特性
- 完全支持Protocol Buffer语法(proto3)
- 解析消息、枚举、服务和RPC定义
- 处理导入、包和选项
- 保留注释以供文档用途
- 生成抽象语法树(AST)以便于操作
- 使用PHP 8.3构建
用例
- 从protobuf定义生成代码
- 分析和验证protobuf模式
- 构建用于处理协议缓冲区的工具
- 将protobuf支持集成到现有的PHP应用程序中
安装
通过Composer安装此包
composer require butschster/proto-parser
用法
以下是一个使用Proto解析器的快速示例
use Butschster\ProtoParser\ProtoParserFactory; $parser = ProtoParserFactory::create(); $ast = $parser->parse(<<<'PROTO' syntax = "proto3"; package example; message Person { string name = 1; int32 id = 2; string email = 3; } PROTO); // Now you can work with the AST echo $ast->package->name; // Outputs: example echo $ast->topLevelDefs[0]->name; // Outputs: Person
理解抽象语法树(AST)
抽象语法树(AST)是源代码抽象语法结构的树形表示。在此包的上下文中,AST表示Protocol Buffer定义文件的结构。
以下是一个简单.proto
文件的AST示例
syntax = "proto3"; package example; message Person { string name = 1; int32 id = 2; string email = 3; }
相应的AST可能看起来像这样
ProtoNode
├── SyntaxDeclNode (syntax: "proto3")
├── PackageDeclNode (name: "example")
└── MessageDefNode (name: "Person")
├── FieldDeclNode (name: "name", type: "string", number: 1)
├── FieldDeclNode (name: "id", type: "int32", number: 2)
└── FieldDeclNode (name: "email", type: "string", number: 3)
AST中的每个节点都对应于Protocol Buffer定义中的一个特定元素。这种结构允许程序化地轻松遍历和操作Protocol Buffer结构。
AST类图
以下类图提供了Protobuf解析器中可用的节点类的概述。此图显示了不同节点类之间的关系及其属性。
详细用法示例
解析消息
$ast = $parser->parse(<<<'PROTO' syntax = "proto3"; message Person { string name = 1; int32 id = 2; string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; } PROTO); $personMessage = $ast->topLevelDefs[0]; echo $personMessage->name; // Outputs: Person foreach ($personMessage->fields as $field) { echo "{$field->name}: {$field->type->type}\n"; } $phoneTypeEnum = $personMessage->enums[0]; echo $phoneTypeEnum->name; // Outputs: PhoneType $phoneNumberMessage = $personMessage->messages[0]; echo $phoneNumberMessage->name; // Outputs: PhoneNumber
解析服务
$ast = $parser->parse(<<<'PROTO' syntax = "proto3"; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } PROTO); $service = $ast->topLevelDefs[0]; echo $service->name; // Outputs: Greeter foreach ($service->methods as $method) { echo "{$method->name}: {$method->inputType->name} -> {$method->outputType->name}\n"; }
处理导入和选项
$ast = $parser->parse(<<<'PROTO' syntax = "proto3"; import "google/protobuf/timestamp.proto"; import public "other.proto"; package mypackage; option java_package = "com.example.foo"; option optimize_for = SPEED; message MyMessage { string my_string = 1; google.protobuf.Timestamp time = 2; } PROTO); foreach ($ast->imports as $import) { echo "Import: {$import->path} (Modifier: {$import->modifier})\n"; } echo "Package: {$ast->package->name}\n"; foreach ($ast->options as $option) { echo "Option: {$option->name} = {$option->options[0]->value}\n"; }
示例:生成DTO类
AST的一个强大用例是从Protocol Buffer定义生成数据传输对象(DTO)类。您可以使用代码生成器(如nette/php-generator
)根据AST创建PHP类。
以下是如何使用AST生成PHP类的示例
use Butschster\ProtoParser\ProtoParser; use Butschster\ProtoParser\ProtoParserFactory; use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\PhpFile; $parser = ProtoParserFactory::create(); $ast = $parser->parse(<<<'PROTO' syntax = "proto3"; message Person { string name = 1; int32 age = 2; repeated string hobbies = 3; } PROTO, ); // Generate PHP class $file = new PhpFile(); $file->setStrictTypes(); $namespace = $file->addNamespace('App\Dto'); $class = $namespace->addClass('Person'); $class->setFinal() ->setReadOnly(); foreach ($ast->topLevelDefs[0]->fields as $field) { $type = match ($field->type->type) { 'string' => 'string', 'int32' => 'int', default => 'mixed', }; $property = $class->addProperty($field->name) ->setType($type) ->setPublic(); if ($field->modifier === \Butschster\ProtoParser\Ast\FieldModifier::Repeated) { $property->setType('array'); } } echo $file;
这将生成一个PHP类,如下所示
<?php declare(strict_types=1); namespace App\Dto; final readonly class Person { public string $name; public int $age; public array $hobbies; }
使用AST进行代码生成有许多可能性。更多用例
-
生成具有验证属性的DTO:您可以使用AST生成带有内置验证的DTO,使用Symfony Validator属性。
-
生成OpenAPI模式:通过解析服务RPC选项,您可以自动生成API端点的OpenAPI(Swagger)模式文档,保持API文档与protobuf定义同步。
-
生成干净的JSON序列化DTO:创建优化了JSON序列化的DTO,确保PHP应用程序与其他系统或前端之间数据传输的高效性。
-
静态分析工具集成:使用AST创建自定义PHPStan或Psalm规则,以分析PHP代码库中的protobuf使用情况,增强类型安全并尽早捕获潜在问题。
-
代码迁移辅助:利用AST帮助自动化当protobuf定义发生变化时的代码迁移,确保PHP代码与不断发展的protobuf模式保持同步。
AST节点类
本文档提供了Protobuf解析器中可用节点类的概述。这些类代表Protobuf定义文件的不同元素。
ProtoNode
表示Protobuf文件的根节点。
final readonly class ProtoNode implements NodeInterface { public function __construct( public SyntaxDeclNode $syntax, public array $imports = [], public ?PackageDeclNode $package = null, public array $options = [], public array $topLevelDefs = [], ) {} }
$syntax
:Protobuf 文件的语法声明。$imports
:表示导入文件的ImportDeclNode
对象数组。$package
:表示包声明的可选PackageDeclNode
。$options
:表示文件级别选项的OptionDeclNode
对象数组。$topLevelDefs
:表示顶级定义(消息、枚举、服务)的数组。
SyntaxDeclNode
表示 Protobuf 文件的语法声明。
final readonly class SyntaxDeclNode implements NodeInterface { public function __construct( public string $syntax, public array $comments = [], ) {} }
$syntax
:语法版本(例如,“proto3”)。$comments
:与语法声明相关联的CommentNode
对象数组。
ImportDeclNode
表示 Protobuf 文件中的导入语句。
final readonly class ImportDeclNode implements NodeInterface { public function __construct( public string $path, public ?ImportModifier $modifier = null, public array $comments = [], ) {} }
$path
:导入文件的路径。$modifier
:可选的ImportModifier
枚举值(弱或公共)。$comments
:与导入语句相关联的CommentNode
对象数组。
PackageDeclNode
表示 Protobuf 文件中的包声明。
final readonly class PackageDeclNode implements NodeInterface { public function __construct( public string $name, public array $comments = [], ) {} }
$name
:包的名称。$comments
:与包声明相关联的CommentNode
对象数组。
OptionDeclNode
表示 Protobuf 文件中的选项声明。
final readonly class OptionDeclNode implements NodeInterface { public function __construct( public ?string $name, public array $comments = [], public array $options = [], ) {} }
$name
:选项的名称。$comments
:与选项相关联的CommentNode
对象数组。$options
:表示嵌套选项的OptionNode
对象数组。
MessageDefNode
表示 Protobuf 文件中的消息定义。
final readonly class MessageDefNode implements NodeInterface { public function __construct( public string $name, public array $fields = [], public array $messages = [], public array $enums = [], public array $comments = [], ) {} }
$name
:消息的名称。$fields
:表示消息字段的FieldDeclNode
对象数组。$messages
:表示嵌套MessageDefNode
对象数组的数组。$enums
:表示在消息内定义的EnumDefNode
对象数组。$comments
:与消息相关联的CommentNode
对象数组。
FieldDeclNode
表示消息内的字段声明。
final readonly class FieldDeclNode implements NodeInterface { public function __construct( public FieldType $type, public string $name, public int $number, public ?FieldModifier $modifier = null, public array $options = [], public array $comments = [], ) {} }
$type
:表示字段类型的FieldType
对象。$name
:字段的名称。$number
:字段编号。$modifier
:可选的FieldModifier
枚举值(必需、可选或重复)。$options
:表示字段选项的OptionDeclNode
对象数组。$comments
:与字段相关联的CommentNode
对象数组。
EnumDefNode
表示 Protobuf 文件中的枚举定义。
final readonly class EnumDefNode implements NodeInterface { public function __construct( public string $name, public array $fields = [], public array $comments = [], ) {} }
$name
:枚举的名称。$fields
:表示枚举值EnumFieldNode
对象的数组。$comments
:与枚举相关联的CommentNode
对象数组。
EnumFieldNode
表示枚举定义内的字段。
final readonly class EnumFieldNode implements NodeInterface { public function __construct( public string $name, public int $number, public array $options = [], public array $comments = [], ) {} }
$name
:枚举值的名称。$number
:与枚举字段相关联的数字值。$options
:表示字段选项的OptionDeclNode
对象数组。$comments
:与枚举字段相关联的CommentNode
对象数组。
ServiceDefNode
表示 Protobuf 文件中的服务定义。
final readonly class ServiceDefNode implements NodeInterface { public function __construct( public string $name, public array $methods = [], public array $options = [], public array $comments = [], ) {} }
$name
:服务的名称。$methods
:表示服务方法RpcDeclNode
对象的数组。$options
:表示服务选项的OptionDeclNode
对象数组。$comments
:与服务相关联的CommentNode
对象数组。
RpcDeclNode
表示服务内的 RPC(远程过程调用)声明。
final readonly class RpcDeclNode implements NodeInterface { public function __construct( public string $name, public RpcMessageType $inputType, public RpcMessageType $outputType, public array $options = [], public array $comments = [], ) {} }
$name
:RPC 方法的名称。$inputType
:表示输入消息类型的RpcMessageType
对象。$outputType
:表示输出消息类型的RpcMessageType
对象。$options
:表示 RPC 选项的OptionDeclNode
对象数组。$comments
:与 RPC 相关联的CommentNode
对象数组。
MapFieldDeclNode
表示消息内的映射字段声明。
final readonly class MapFieldDeclNode implements NodeInterface { public function __construct( public FieldType $keyType, public FieldType $valueType, public string $name, public int $number, public array $options = [], public array $comments = [], ) {} }
$keyType
:表示映射键类型的FieldType
对象。$valueType
:表示映射值类型的FieldType
对象。$name
:映射字段的名称。$number
:字段编号。$options
:表示字段选项的OptionDeclNode
对象数组。$comments
:与地图字段相关联的CommentNode
对象数组。
OneofFieldNode
表示一个 oneof 组内的字段。
final readonly class OneofFieldNode implements NodeInterface { public function __construct( public FieldType $type, public string $name, public int $number, public array $options = [], ) {} }
$type
:表示字段类型的FieldType
对象。$name
:字段的名称。$number
:字段编号。$options
:表示字段选项的OptionDeclNode
对象数组。
CommentNode
表示 Protobuf 文件中的注释。
final readonly class CommentNode { public function __construct( public string $text, ) {} }
$text
:注释的文本内容。
ReservedNode
表示消息或枚举中的保留语句。
final readonly class ReservedNode implements NodeInterface { public function __construct( public array $ranges, ) {} }
$ranges
:表示保留字段或数字的ReservedRange
或ReservedNumber
对象数组。
OptionNode
表示选项声明中的单个选项。
final readonly class OptionNode { public function __construct( public string $name, public mixed $value, public array $comments = [], ) {} }
$name
:选项的名称。$value
:选项的值(可以是混合类型或另一个OptionDeclNode
)。$comments
:与选项相关联的CommentNode
对象数组。
Contributing
欢迎贡献!请遵循以下指南向项目贡献
- 从
master
分支创建您的分支以克隆仓库。 - 确保您的代码遵循 PSR-12 编码标准。
- 如果您正在修改解析器语法
- 编辑
ebnf.pp2
文件以进行更改。 - 运行单元测试以重新生成
src/grammar.php
文件。 - 确保所有现有测试都通过,并添加新测试以覆盖您的更改。
- 编辑
- 如果您添加或更改功能,请更新 README 中的文档。
- 编写清晰简洁的提交信息。
- 提交一个包含您更改详细描述的拉取请求。
License
本项目采用 MIT 许可证授权。