butschster/proto-parser

Proto解析器是一个库,可以将Protocol Buffers文件解析成抽象语法树(AST)

1.0.6 2024-08-18 12:32 UTC

This package is auto-updated.

Last update: 2024-09-18 12:40:28 UTC


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进行代码生成有许多可能性。更多用例

  1. 生成具有验证属性的DTO:您可以使用AST生成带有内置验证的DTO,使用Symfony Validator属性。

  2. 生成OpenAPI模式:通过解析服务RPC选项,您可以自动生成API端点的OpenAPI(Swagger)模式文档,保持API文档与protobuf定义同步。

  3. 生成干净的JSON序列化DTO:创建优化了JSON序列化的DTO,确保PHP应用程序与其他系统或前端之间数据传输的高效性。

  4. 静态分析工具集成:使用AST创建自定义PHPStan或Psalm规则,以分析PHP代码库中的protobuf使用情况,增强类型安全并尽早捕获潜在问题。

  5. 代码迁移辅助:利用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:表示保留字段或数字的 ReservedRangeReservedNumber 对象数组。

OptionNode

表示选项声明中的单个选项。

final readonly class OptionNode
{
    public function __construct(
        public string $name,
        public mixed $value,
        public array $comments = [],
    ) {}
}
  • $name:选项的名称。
  • $value:选项的值(可以是混合类型或另一个 OptionDeclNode)。
  • $comments:与选项相关联的 CommentNode 对象数组。

Contributing

欢迎贡献!请遵循以下指南向项目贡献

  1. master 分支创建您的分支以克隆仓库。
  2. 确保您的代码遵循 PSR-12 编码标准。
  3. 如果您正在修改解析器语法
    • 编辑 ebnf.pp2 文件以进行更改。
    • 运行单元测试以重新生成 src/grammar.php 文件。
    • 确保所有现有测试都通过,并添加新测试以覆盖您的更改。
  4. 如果您添加或更改功能,请更新 README 中的文档。
  5. 编写清晰简洁的提交信息。
  6. 提交一个包含您更改详细描述的拉取请求。

License

本项目采用 MIT 许可证授权。